[Tool] Package Bundler — potentially shrink your app size drastically with this tool

Package Bundler

Github: GitHub - robertklep/package-bundler: Bundle a Node.js package into a single file.

This tool can be used to create single-file Node.js packages from existing (NPM-hosted) packages.

I’ll explain why this can be useful with an example:

At the moment I’m working on an app that depends on homey-api. Just adding this package to my project made the installation time of my app (homey app run) explode into multiple dozens of seconds.

The reason is two-fold: homey-api and all its dependencies take up 7MB of disk space, and consists of about 3500 files (this is the main performance killer when it comes to Homey apps).

By bundling homey-api into a single file, its size is cut down to about 600K, and just a single file. See below for a “benchmark”.

How to use

Follow the installation instructions on the Github page.

To bundle homey-api, run the following command:

$ package-bundler homey-api

This will create a directory homey-api in the current directory that contains the bundled file (index.js). If you move this directory into your app directory, you can use it from your app code as a regular relative import:

// app.js
const { HomeyAPIApp } = require('./homey-api');

Don’t forget to remove homey-api as a dependency of your app, otherwise it will still be uploaded:

$ npm uninstall homey-api

(if you for some reason want to remove the package(s) manually, don’t just remove them from node_modules without updating package.json, because you’ll get an error about missing requirements when you run homey app run).

Some remarks

  • It should be no problem to publish apps to the App Store with bundled packages like this.
  • Not all packages can be bundled, especially packages that use dynamic imports.
  • Not all packages have to be bundled. Just pick the ones that are large and/or consist of a lot of files.
  • A bundled package always requires a relative import, so if you want to use it from a driver/device file, you need to use something like require('../../homey-api') (or possibly require('/homey-api')).
  • Instead of placing bundled packages directly in your app directory, I would suggest placing them in a subdirectory (say modules/) to keep things nicely separated. Adjust your import paths accordingly.
  • I haven’t tested this script very well and wrote it mainly for my own purpose. If you run into any issues, hit me up.

“Benchmark”

Let’s test how long it takes for homey app run to install/start/stop an app that does absolutely nothing other than loading homey-api.

  • Unbundled:
$ time homey app run
✓ Pre-processing app...
 — App archive size: 6.9 MB, 3521 files
✓ Running `my.test.app`, press CTRL+C to quit
✓ Uninstalling `my.test.app`...
✓ Homey App `my.test.app` successfully uninstalled
homey app run   1.71s  user 1.11s system 6% cpu 40.349 total
  • Bundled:
$ time homey app run
✓ Pre-processing app...
 — App archive size: 703 KB, 10 files
✓ Running `my.test.app`, press CTRL+C to quit
✓ Uninstalling `my.test.app`...
✓ Homey App `my.test.app` successfully uninstalled
homey app run   0.77s  user 0.16s system 13% cpu 6.631 total

So the unbundled version takes 40 seconds, the bundled version takes 6 seconds. And that’s including uninstallation too, which is relatively slow. The time until the app is uploaded, installed and started takes about 35 seconds unbundled, and 2 seconds bundled.

11 Likes

I would love to use this, but since I’m on Windows I don’t know how to use this. Looks like this will only work on Linux?

Thanks though, I can imagine this could improve many apps!

I think it might work when you install WSL. I run this script on macOS myself.

Or, if you don’t get it working, let me know which module(s) you would like to try with it and I can bundle them for you.

I’ve rewritten the script from a Bash script to a Node.js script, which should hopefully make it easier to install and run on Windows. See the updated installation instructions on Github.

3 Likes

@robertklep great find! Do you think node-fetch would benefit as well?

As @robertklep says, WSL is the way to go on Windows. It took me about 2 hours of hacking this morning but I got there eventually. I would put some steps to make it work but I’m not entirely sure how I got there :slight_smile:

Edit: I have just seen @robertklep update where he has converted it to a node script so should be really simple now and not require WSL If only I had waited an extra hour before starting :laughing:

2 Likes

Sorry :joy:

I’m also planning to remove the external dependency on @vercel/ncc, but that’s not a major change.

Yes, I just tried, but since only CommonJS packages are supported (as of yet) you need to install node-fetch@2 (as per the installation instructions):

$ package-bundler node-fetch@2

The bundled package that comes out is 300K and a quick test shows it works just fine.

Thanks for confirmation. Tempted to try it on my Homewizard app as its over 1000 lines and 4Mbyte total. So curious if it will benefit from it. Did you also see a drop in memory usage for your app or does it only affect the used storage for the application?

I haven’t really checked if it uses less memory, but it probably won’t (or at least not a considerable amount). But it makes the developer experience so much better, no more 40-second waits to upload your app to Homey after a small change.

Agreed, during development and testing of my app stop/start is eh…taking time indeed. :slight_smile:

Done.

1 Like

Haven’t been able to try this yet, but I think if I wait a bit more all will be done automatically :stuck_out_tongue_winking_eye:

I can’t get this to work, undoubtably I am doing something wrong…

image

So far so good. I then removed the existing luxon folder from the node_module folder, and changes the “const luxon = require(‘luxon’);” to “const luxon = require(‘./luxon’);” in the app.js

But app validation/installation now fails:

Any hints are much appreciated.

I’m no node.js expert but aren’t your dependencies defined in package.json?

Not with Homey apps afaik. The example in the opening post also mentions the app.js.

Did you try?

As @Robin_van_Kekem said, make sure you remove the dependency line in package.json
If you used npm to uninstall tha package it would have done that for you, but if you just deleted the folder then it will still be there.

Yup, that solved it. Thanks!

2 Likes

It’s in the fine manual :wink:

2 Likes