JS/TS Managing alternative implementations with RollupJS
Johannes Zillmann
Posted on May 24, 2020
Short post about a trivial thing to do. I’m in the JS/Electron 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 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:
- Reading and Writing app config => ElectronStore / Local Storage
- Reading and Writing files => Node FS API / Local Storage
Basic Structure
Simple. Lets just focus on the app config.
- I defined a common ‘interface’ (AppConfig)
- One implementation wrapping ElectronStore (ElectronAppConfig)
- A second implementation wrapping the local storage (LocalAppConfig).
Most Naive Approach
I just kept all 3 classes under /src
with a factory method:
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 to steer the __electronEnv__
variable:
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
:
"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 to alias the index.js of the desired implementation at build time in rollup.config.js:
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:
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 came to rescue:
Here is the rollup.config.js
part:
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
- Multiple packages (with individual dependencies) managed with… lets say Lerna…
If you have any feedback or inspiration, let me know!
Posted on May 24, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.