JS/TS Managing alternative implementations with RollupJS
Tags: javascript, rollup, electron, modularity
Published: 2020-05-24 18:24:37

---
title: JS/TS Managing alternative implementations with RollupJS
published: true
date: 2020-05-24 18:01:17 UTC
tags: javascript,rollup,electron,modularity
canonical_url: 
---

Short post about a trivial thing to do. I’m in the JS/[Electron](https://www.electronjs.org) world. I just decided I want to package my app for Electron but for a regular Browser as well. Why ?

- A) I can do a demo version of the app on the web!
- B) I can use [Cypress](https://www.cypress.io) for testing it!

Will see how far this goes, but currently i’m using only two Electron/Desktop features that can be easily mimicked in an browser environment:

1. Reading and Writing app config => [ElectronStore](https://github.com/sindresorhus/electron-store) / [Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)
2. Reading and Writing files => [Node FS API](https://nodejs.org/api/fs.html) / [Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)

### Basic Structure

Simple. Lets just focus on the app config.

- I defined a common ‘interface’ (_AppConfig_)
- One implementation wrapping [ElectronStore](https://github.com/sindresorhus/electron-store) (_ElectronAppConfig_)
- A second implementation wrapping the local storage (_LocalAppConfig_).

### Most Naive Approach

I just kept all 3 classes under `/src` with a factory method:

```javascript
export function createAppConfig(appConfigSchema) {
  if (__electronEnv__) {
    const ElectronStore = require('electron-store'); 
    return new ElelectronAppConfig(new ElectronStore({schema:appConfigSchem}));
  } else {
    const defaults = Object
      .keys(appConfigSchema)
      .reduce((o, key) => ({...o, [key]: appConfigSchema[key]['default'] }),{});
    return new LocalAppConfig(window.localStorage, defaults);
  }
}
```

Then in the `rollup.config.js` i’m using the [plugin-replace](https://github.com/rollup/plugins/tree/master/packages/replace) to steer the `__electronEnv__` variable:

```javascript
import replace from '@rollup/plugin-replace';

const electronEnv = !!process.env.ELECTRON;

plugins: [
  replace({__electronEnv__: electronEnv}),
]
```

And finally i enrich my NPM electron tasks with then env variable in the `package.json`:

```json
"electron": "ELECTRON=true run-s build pure-electron",
```

That’s it for the naive approach. It’s working most of the times (sometimes there is a hiccup with a `require not found error`, but a rebuild usually solves it).

Anyway, the purist in me, wanted a clearer structure and also the inline _require_ _statements_ seemed odd.

### Moving to a more satisfactory approach

Have another folder next to `/src`, let’s called it `/includes` with three sub-folders:

- **api** : _AppConfig_, …
- **_electron_** : _index.js_ (contain factory methods for all electron implementations), _ElectronAppConfig_, …
- **browser** : _index.js_ (contain factory methods for all browser implementations), _LocalAppConfig_, …

Now use [plugin-alias](https://github.com/rollup/plugins/tree/master/packages/alias) to alias the _index.js_ of the desired implementation at build time in rollup.config.js:

```javascript
import alias from '@rollup/plugin-alias';
const electronEnv = !!process.env.ELECTRON;
const storagePackage = electronEnv ? 'electron' : 'browser';

plugins: [
  alias({
    entries: [
      { find: 'storage', replacement: `./includes/${storagePackage}/index.js` }
    ]
  })
]
```

And access the implementation in your main code:

```javascript
import { createAppConfig } from 'storage';

const appConfig = createAppConfig(appConfigSchema);
```

Easy. Not too much gain here, but some clearer structure!

### And now in Typescript…

Once i moved to the approach above, i thought ‘Ok, lets try typescript’. Cause that’s an obvious thing to do if you’re talking about _interfaces_ and _implementations_, right ?

I failed using the exact same approach but luckily the typescript [path-mapping](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping) came to rescue:

Here is the `rollup.config.js` part:

```javascript
import typescript from '@rollup/plugin-typescript';

plugins: [
 typescript({ target: 'es6', baseUrl: './', paths: { storage: [`./includes/${storagePackage}/index.js`] } })
]
```

Imports work the same as in the previous approach!

### Final Words

Not sure if i delivered on the promise of _shortness_, but finding the second/third approaches took me longer than expected and drove me almost nuts. Part i blame on my inexperience in the JS world, part is that the search space for such a problem seems heavily convoluted. That said, there might be a couple of alternatives worth investigating:

- Dynamic Modules: [https://medium.com/@leonardobrunolima/javascript-tips-dynamically-importing-es-modules-with-import-f0093dbba8e1](https://medium.com/@leonardobrunolima/javascript-tips-dynamically-importing-es-modules-with-import-f0093dbba8e1)
- Multiple packages (with individual dependencies) managed with… lets say [Lerna](https://github.com/lerna/lerna)…

If you have any feedback or inspiration, let me know!

* * *