Разрабатываем расширения для VS Code на Kotlin/JS

Kate

Administrator
Команда форума
Kotlin/JS – это технология, позволяющая транслировать код, написанный на Kotlin, в JavaScript. Мне не удалось найти информации о том, как написать своё расширение для Visual Studio Code, популярного редактора кода, используя Kotlin, поэтому я задался вопросом, а возможно ли это? Какие проблемы нас ждут?

ad94b863e7e0af802b1a420abced7349.png

TLDR: да, возможно

Disclaimer: это не гайдлайн по написанию расширений, а лишь туториал по подготовке инфраструктуры

Для разработки расширений на TypeScript представлены декларации типов в формате d.ts. Мы можем переиспользовать их в Kotlin/JS, благодаря инструменту Dukat. Инструмент генерирует из d.ts деклараций аналогичные на Kotlin.

Приступим к созданию проекта.

Создание проекта​

Воспользуемся генератором проектов из Intellij IDEA, выбрав Kotlin – Node.JS application – Gradle Kotlin – IR Kotlin/JS Compiler. IR в данном контексте – это новый бекенд компилятора, который в скором времени станет бекендом по умолчанию.

Демонстрация создания проекта
Демонстрация создания проекта
Получим build.gradle.kts файл примерно следующего содержания

plugins {
kotlin("js") version "1.5.31"
}

group = "com.alikhachev"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
testImplementation(kotlin("test"))
}

kotlin {
js(IR) {
binaries.executable()
nodejs {

}
}
}
Нам понадобится экспортировать функции, что пока является экспериментальной фичей языка, поэтому добавим в блок kotlin следующий код, разрешающий использовать в коде аннотацию @JsExport:

sourceSets {
all {
languageSettings {
optIn("kotlin.js.ExperimentalJsExport")
}
}
}
Kotlin Gradle plugin, очевидно, не знает о том, что мы собираемся писать расширение для VS Code, поэтому добавим вручную необходимые записи в package.json, которые нужны для работы расширения. Для этого добавим в блок конфигурации js следующий код

compilations.named("main") {
packageJson {
customField("categories", listOf("Other"))
customField("activationEvents", listOf("onCommand:helloworld.helloWorld"))
customField("contributes", mapOf("commands" to listOf(mapOf("command" to "helloworld.helloWorld", "title" to "Hello World"))))
customField("engines", mapOf("vscode" to "^1.60.0"))
customField("displayName", "HelloWorld")
customField("description", "My first extension")
}
}
Также добавим упомянутую выше зависимость на @types/vscode в блок dependencies:

implementation(npm("@types/vscode", "^1.60.0", generateExternals = true))
Указывая generateExternals = true, мы просим Kotlin Gradle Plugin сгенерировать Kotlin-декларации из d.ts для этой зависимости с помощью Dukat.

К сожалению, для этой зависимости генерируется немного некорректный код, который не компилируется, поэтому находим соответствующий issue в Dukat и голосуем за него. А до момента, пока баг не исправят, обойдем проблему ручным исправлением сгенерированных файлов.

Выставим generateExternals = false и сгенерируем их вручную, выполнив команду ./gradlew generateExternals. Декларации сгенерировались в директорию externals нашего проекта.

Создадим Kotlin/JS модуль vscode, перенесем сгенерированный код в него (выносить в отдельный модуль желательно для того, чтобы механизм dead code elimination смог корректно вырезать неиспользуемый код из итогового js-файла). Подправленный код, как и сам модуль, не привожу, заинтересованный читатель может посмотреть его в репозитории, ссылка на который будет в конце статьи.

Далее заменим декларацию зависимости @types/vscode зависимостью на новый модуль:

implementation(project(":vscode"))
Также сразу напишем задачу, которая позволит в один клик собрать расширение и установить его в VS Code:

tasks.register("installExtension", Sync::class) {
dependsOn("productionExecutableCompileSync")
from({
kotlin.js().compilations.named("main").map { it.npmProject.dir }
}) // build/js/packages/<имя модуля>
into {
project.provider { File(providers.systemProperty("user.home").get()).resolve(".vscode/extensions").resolve(project.name) }
} // ~/.vscode/extensions/<имя модуля>
doFirst {
logger.info("Installing VS Code extension into $destinationDir")
}
}
Задача будет брать готовый npm модуль и перекладывать его в директорию с расширениями VS Code ~/.vscode/extensions/

Код расширения​

Удалим код, который нам сгенерировала IDEA, он нам не нужен. Создадим файл extension.kt в исходниках, и напишем код, аналогичный тому, который создаётся в туториале от Microsoft Your First Extension:

@JsExport
fun activate(context: vscode.ExtensionContext) {
val disposable = vscode.commands.registerCommand("helloworld.helloWorld", {
vscode.window.showInformationMessage("Hello World from Kotlin/JS!")
})

context.subscriptions.asDynamic().push(disposable)
}

@JsExport
fun deactivate() {

}
Запустим нашу задачу по сборке и установке ./gradlew installExtension и проверим работу расширения, запустив VS Code и исполнив команду Hello World. Радуемся своему первому работающему расширению на Kotlin/JS :)

ef4c25d2f93a4afefa120d7339e88f70.gif

Отладка расширения​

Какая серьёзная разработка без отладки?

Добавим в build.gradle.kts задачу, которая будет запускать VS Code в специальном режиме отладки, подхватывая наше расширение без необходимости установки:

tasks.register("debugExtension", Exec::class) {
dependsOn("developmentExecutableCompileSync")
val path = kotlin.js().compilations.named("main").map { it.npmProject.dir }.get()
commandLine("code", "--inspect-extensions=9229", "--extensionDevelopmentPath=$path")
}
Также создадим Run configuration в IDEA:

IDEA run configuration
IDEA run configuration
Теперь мы можем начать сеанс отладки, запустив ./gradlew debugExtension и подключившись дебаггером через созданную конфигурацию.

Заключение​

Благодарю за прочтение. У вас может возникнуть вопрос, а зачем, собственно, это всё нужно при наличии TypeScript? Например, вы любите Kotlin и хотите использовать библиотеки, написанные используя Kotlin Multiplatform или Kotlin/JS, тогда описанный путь сделает это для вас удобнее. Или вы такой же, как и я, больной на голову интересующийся чем-то новым инженер ;)

GitHub

 
Сверху