Different ways to distribute and integrate Kotlin/JS library
Tags: kotlin, kotlinmultiplatform, javascript, kotlinjs
Published: 2022-07-11 20:24:11

> Note that examples in this post use [Kotlin 1.7.0](https://kotlinlang.org/docs/whatsnew17.html)

In the previous [post](https://dev.to/touchlab/jsexport-guide-for-exposing-kotlin-to-js-20l9) in the Kotlin/JS series, we learned about `@JsExport` to expose Kotlin code on the JS side. Now, we would look at various ways to distribute a JS library code through a KMP setup.

## Table Of Contents
  * [Gradle Setup](#gradle-setup)
    * [Webpack](#webpack)
    * [Node Module](#node-module)
      * [npm-publish](#npm-publish)
  * [Using the library](#using-library)
    * [Use webpack executable](#webpack-executable)
      * [HTML](#webpack-html)
      * [JavaScript](#webpack-js)
      * [TypeScript](#webpack-ts)
    * [Use as node module](#node-module)
      * [HTML](#node-html)
      * [JavaScript](#node-js)
      * [TypeScript](#node-ts)

## Gradle Setup<a name="gradle-setup"></a>
> If you don't already have a `JS` target in your KMP library project, then you should check out [this blog post](https://dev.to/touchlab/add-kotlinjs-support-to-your-kmm-library-48d9) first.

Let's look at common options to use for `JS` target,
```kotlin
js(IR) {
    browser()
    nodejs()
    binaries.library()
    binaries.executable()
}
```
- **browser()**
It sets the JavaScript target execution environment as `browser`. It provides a Gradle task—`jsBrowserTest` that runs all `js` tests inside the browser using `karma` and `webpack`.

- **nodejs()**
It sets the JavaScript target execution environment as `nodejs`. It provides a Gradle task—`jsNodeTest` that runs all `js` tests inside `nodejs` using the built-in test framework.

- **binaries.library()**
It tells the Kotlin compiler to produce Kotlin/JS code as a distributable node library. Depending on which target you've used along with this, you would get Gradle tasks to generate library distribution files.


    ```sh
    Kotlin browser tasks
    --------------------
    jsBrowserDevelopmentLibraryDistribution
    jsBrowserDevelopmentLibraryPrepare
    jsBrowserProductionLibraryDistribution
    jsBrowserProductionLibraryPrepare

    Kotlin node tasks
    -----------------
    jsNodeDevelopmentLibraryDistribution
    jsNodeDevelopmentLibraryPrepare
    jsNodeDevelopmentRun
    jsNodeProductionLibraryDistribution
    jsNodeProductionLibraryPrepare
    jsNodeProductionRun
    jsNodeRun
    ```

    Either`jsBrowserProductionLibraryDistribution` or `jsNodeProductionLibraryDistribution` task generates output files in `<YourLibModule>/build/productionLibrary` folder

- **binaries.executable()**
It tells the Kotlin compiler to produce Kotlin/JS code as webpack executable `.js` files. Enabling this option generates the following Gradle tasks with the `browser()` environment.

    ```
    jsBrowserDevelopmentExecutableDistributeResources
    jsBrowserDevelopmentExecutableDistribution
    jsBrowserDevelopmentRun - start development webpack dev server
    jsBrowserDevelopmentWebpack - build webpack development bundle
    jsBrowserDistribution
    jsBrowserProductionExecutableDistributeResources
    jsBrowserProductionRun - start production webpack dev server
    jsBrowserProductionWebpack - build webpack production bundle
    jsBrowserRun
    jsBrowserWebpack
    ```
    
    Task `jsBrowserDistribution` generates webpack bundle `js` file along with `.map` file in `<YourLibModule>/build/distributions` folder.

### Webpack<a name="webpack"></a>
Webpack is a [JS bundler tool](https://webpack.js.org/). It allows the creation of a single executable JS file combining all the dependencies.

As seen above in the Gradle setup section, you can output your `Kotlin/JS` library as a single executable by using `browser()` and `binaries.executable()` options in `js(IR)` target. By default, the bundle `.js` file and exported library name are the same as the module name. For example, for a module named _shared_, a `shared.js` file gets generated. It would have `exports.shared=...` that reflects the name of the library that one would use when consuming this library in a JS/TS app.
```
....."object"==typeof exports?exports.shared=n().....
```

The `browser()` function provides a DSL that you can use to update [KotlinWebpack](https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt) task for various custom configurations. For example, you can change the file and library name as below,
```
browser {
    webpackTask {
        outputFileName = "kmp_lib.js"
        output.library = "kmpLib"
    }
}
```
You can explore more options on your own.

### Node Module<a name="node-module"></a>
Kotlin/JS library can also be published as a node module, and installed via `npm` in an app.

As mentioned above in the Gradle setup section, when you run `jsNodeProductionLibraryDistribution`, it generates the following `node-module` related files,
    - `.js` file each for the library and all dependencies
    - `.js.map` file for each `.js` file
    - Typescript `.d.ts` file
    - `package.json`

_Note_ that node module cannot have upper case character in the name. An error or warning may appear if the module name has a capital character. We can declare a `moduleName` in the `js(IR)` target to avoid that,
```
js(IR) {
    moduleName = "kmp-lib"
    ....
}
```

#### `npm-publish` plugin<a name="npm-publish"></a>
By default, there is no quick way to publish the library output as a node module to maven.

[npm-publish](https://github.com/mpetuska/npm-publish) is a popular library by [Martynas Petuška](https://twitter.com/MartynasPetuska) that helps with NPM publishing. It also provides various configuration options under the `npmPublish` Gradle task.

The plugin generates a tarball that we can use to install the library locally without publishing first. The `packJsPackage` task also logs a readable output of tarball content and details.

```sh
npm notice 
npm notice 📦  shared@0.1.0
npm notice === Tarball Contents === 
npm notice 291B  kmp-lib.d.ts                     
npm notice 1.4kB kmp-lib.js                       
npm notice 205B  kmp-lib.js.map                   
npm notice 1.4kB kotlin-kotlin-stdlib-js-ir.js    
npm notice 551B  kotlin-kotlin-stdlib-js-ir.js.map
npm notice 95B   package.json                     
npm notice === Tarball Details === 
npm notice name:          shared                                  
npm notice version:       0.1.0                                   
npm notice filename:      shared-0.1.0.tgz                        
npm notice package size:  1.6 kB                                  
npm notice unpacked size: 3.9 kB                                  
shared-0.1.0.tgz
npm notice shasum:        c3c5e0a7a6d99cf1b7632fcfc4cef2e0b9052cb2
npm notice integrity:     sha512-dZa4uNPRMjyny[...]JjzzNAiP7abHw==
npm notice total files:   6                                       
npm notice 
```

If you're planning to publish a `Kotlin/JS` library as a node module, then this is a must-have plugin in your project.

---
## Using the library<a name="using-library"></a>
We will now look at various ways to integrate the Kotlin/JS library into web applications.

For example, we have a `Greeting` class and a `greet()` method under a package `jabbar.jigariyo.kmplibrary`

```kotlin
package jabbar.jigariyo.kmplibrary

import kotlin.js.JsExport

@JsExport
class Greeting {
    fun greet(): String {
        return "Hello, JS!"
    }
}
```

### Use webpack executable<a name="webpack-executable"></a>
We will now look at using the generated webpack binary executable bundle file in HTML and react JS app.

#### HTML<a name="webpack-html"></a>
We can simply embed the `js` file as a script under the `<script>` tag of the HTML body.

```html
<html>

<head>
    <meta charset="utf-8">
    <title>Including a External JavaScript File</title>
</head>

<body>
    <!-- Here I have copied the js file in the root folder of this html file -->
    <script type="text/javascript" src="kmp_lib.js"></script>
    <script>
        var kmpLib = this['kmpLib'];
        var greeting = new kmpLib.jabbar.jigariyo.kmplibrary.Greeting();
        console.log(greeting.greet());
    </script>
</body>

</html> 
```

Two important things to note here are,
- Using the library name to get a reference `var kmpLib = this['kmpLib']`
- Getting reference of `Greeting` class using package name after the `library` reference `kmpLib.jabbar.jigariyo.kmplibrary.Greeting()`

Once the `html` file is open in the browser, it would log `Hello, JS!` in console output.

#### JavaScript<a name="webpack-js"></a>
For the JavaScript example, I've a React app.

To keep this simple, I've copied the `distributions` folder in the `src` directory of the React app.

As the next step, import the `library` module in the `App.js` and use it

```javascript
import kmpLib from './distributions/kmp_lib';
......

const greeting = new kmpLib.jabbar.jigariyo.kmplibrary.Greeting()
console.log(greeting.greet())

function App() {
  return (
    .......
  );
}

export default App;
```
Once we run the app, `Hello, JS!` gets printed in the console and validates the integration with the library.

> Note that you might have to deal with configuring `eslint` if using React.

#### TypeScript<a name="webpack-ts"></a>
[It's not straightforward](https://tkdodo.eu/blog/calling-java-script-from-type-script) to use just a `js` file without typescript definitions in a `Typescript` app.

I have skipped this integration as it's out of scope for this post.

### Use as node module<a name="node-module"></a>
We will now look at installing and using the generated library files in a React JS and a TS app.

To install the module locally, you can do `npm install <PATH>`. You can either use the path to the tarball (`.tgz`) file generated by the `npm-publish` plugin or the relative path to the `build/productionLibrary` folder of the kmp module.

For example, the installation path for my sample looks like this, 
```
npm install ../shared/build/productionLibrary
```

Once installed, the `package.json` would contain the library under the `dependencies` section,
```json
"dependencies": {
  "kmp-lib": "file:../shared/build/productionLibrary"
}
```
> Note here that the `name` is the same as the `moduleName` we had defined earlier in the above `gradle-setup` section.

#### HTML<a name="node-html"></a>
Using node module in plain HTML is out of scope for this post. There are techniques to make that work if you need to go that route.

#### JavaScript<a name="node-js"></a>
We should be able to `import` the library and use it once installed via `npm`

##### App.js
```javascript
import kmpLib from 'kmp-lib';
......

const greeting = new kmpLib.jabbar.jigariyo.kmplibrary.Greeting()
console.log(greeting.greet())

function App() {
  return (
     .....
  );
}

export default App;
```
Once we run the app, `Hello, JS!` gets printed in the console and validates the integration with the library.

#### TypeScript<a name="node-ts"></a>
Just like the `JavaScript` example above, you have a reference of the library available after `npm install`.

Importing in `Typescript` is slightly different as seen below
##### main.ts
```typescript
import * as kmpLib from 'kmp-lib';

let greeting = new kmpLib.jabbar.jigariyo.kmplibrary.Greeting()
console.log(greeting.greet())
```
Once we run `tsc main.ts` and then `node main.js` commands in the terminal, we would see `Hello, JS!` printed.

---

In this post, we first learned how to distribute a Kotlin/JS library as either a webpack bundle or node module. We later learned how to integrate the library in HTML, JS, or a TS app.

---
Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at [@shaktiman_droid](https://twitter.com/shaktiman_droid) on Twitter, [LinkedIn](https://www.linkedin.com/in/shaktiman-droid/) or [Kotlin Slack](https://slack.kotlinlang.org/). And if you find all this interesting, maybe you'd like to [work with](https://touchlab.co/contact-us/) or [work at](https://touchlab.co/careers-3/) Touchlab.