First, initialize an npm project:
npm init -y
Then, install Karakum and TypeScript as dev dependencies:
npm install karakum typescript -D
Now we can install the library we want to convert, for the sake of simplicity let's install js-file-download:
npm install js-file-download
Now it is time to configure Karakum, let's create karakum.config.json
file and write the following:
{
"input": "node_modules/js-file-download/js-file-download.d.ts",
"output": "generated",
"libraryName": "js-file-download"
}
After that, we can run Karakum to generate Kotlin declarations for us:
npx karakum --config karakum.config.json
When we execute it, we will see something like this in generated/jsFileDownload.kt
:
// @formatter:off
@file:JsModule("js-file-download/js-file-download")
@file:Suppress(
"NON_EXTERNAL_DECLARATION_IN_INAPPROPRIATE_FILE",
)
package js.file.download
external object String /* 'js-file-download' */ {
external fun fileDownload(data: String, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
external fun fileDownload(data: ArrayBuffer, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
external fun fileDownload(data: ArrayBufferView, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
external fun fileDownload(data: Blob, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
}
// @formatter:on
Alas, there are a couple of problems with this generated file. First of all, we have a strange JS module
js-file-download/js-file-download
. It was generated this way because we wrote "libraryName": "js-file-download"
in our configuration and the file we converted is also named js-file-download.d.ts
. By default, Karakum preserves
the original file structure of converted TypeScript files. It may be convenient if we're converting a huge library,
and we want to provide an opportunity for a user to import only JS files he/she really uses. But in our case it is
an overkill, so we can just use js-file-download
as the name for our JS module. To achieve this, add these lines to
karakum.config.json
:
{
"input": "node_modules/js-file-download/js-file-download.d.ts",
"output": "generated",
"libraryName": "js-file-download",
+ "moduleNameMapper": {
+ ".*": "js-file-download"
+ }
}
This instructs Karakum to replace all symbols in JS module names (regex .*
) with js-file-download
.
Another problem is more interesting. We can see some strange construct in the generated code:
external object String /* 'js-file-download' */ {
// ...
}
It is the representation of the following code block in original typings for js-file-download
:
declare module 'js-file-download' {
// ...
}
Namespaces (or modules) in TypeScript are used to co-locate some logic. In Kotlin, there is no equivalent for TypeScript namespaces, but we can't just ignore them during the conversion. Imagine this situation:
declare class MyClass {
}
declare namespace MyNamespace {
class MyClass {
}
}
As you can see, MyClass
and MyNamespace.MyClass
are completely different declarations, so if we ignore namespaces
during conversion, we will get something like this in Kotlin:
external class MyClass
external class MyClass
It is obviously incorrect code, that is why Karakum tries to emulate TypeScript namespaces using Kotlin objects.
But in our case, the declared namespace is actually global, it just tells TypeScript that there is a JS module
js-file-download
. Since we already declared this piece of information in @file:JsModule("js-file-download")
,
we can just skip this namespace declaration. Karakum has the special configuration option for namespace ignoring:
{
"input": "node_modules/js-file-download/js-file-download.d.ts",
"output": "generated",
"libraryName": "js-file-download",
"moduleNameMapper": {
".*": "js-file-download"
},
+ "namespaceStrategy": {
+ "js-file-download": "ignore"
+ }
}
Now we can run Karakum again, and we should get something like this:
// @formatter:off
@file:JsModule("js-file-download")
@file:Suppress(
"NON_EXTERNAL_DECLARATION_IN_INAPPROPRIATE_FILE",
)
package js.file.download
external fun fileDownload(data: String, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
external fun fileDownload(data: ArrayBuffer, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
external fun fileDownload(data: ArrayBufferView, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
external fun fileDownload(data: Blob, filename: String, mime: String = definedExternally, bom: String = definedExternally): Unit
// @formatter:on