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

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 🙂

Отладка расширения
Какая серьёзная разработка без отладки?
Добавим в 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:

Теперь мы можем начать сеанс отладки, запустив ./gradlew debugExtension и подключившись дебаггером через созданную конфигурацию.
Заключение
Благодарю за прочтение. У вас может возникнуть вопрос, а зачем, собственно, это всё нужно при наличии TypeScript? Например, вы любите Kotlin и хотите использовать библиотеки, написанные используя Kotlin Multiplatform или Kotlin/JS, тогда описанный путь сделает это для вас удобнее. Или вы такой же, как и я, больной на голову интересующийся чем-то новым инженер 😉
ссылка на оригинал статьи https://habr.com/ru/articles/582114/
Добавить комментарий