[TIPS] How to use npm modules in Scriptable

Hi, all!
I’m pretty new to Scriptable (just about a few days), but I have some JS/Node background.
I’m going to share some tip and source code I wrote. It might be very helpful to someone like me, if not, oh well.

I wrote some code to use npm modules in a pretty much same manner that you use in NodeJS. I named it Scriptablify because it makes you to be able to use npm modules in Scriptable. Browserify is what makes npm modules useable in browser, so that’s the naming convention I guess.

There seems to be no way to make require() as a global function in Scriptable at the moment. So, any npm modules that uses require statements in a recursive manner, I can’t use them as-is. I would have to bundle every single modules I’d like to use or transform each files to replace require with importModule with some help from tools like browserify. Now that will be some tedious job to begin with and maintenance hell in the long run (not that I need to update those modules frequently).

Then I thought about wzrd.in which is a browserify-as-a-service as it describes itself. Here is what it basically does when you request a npm module via wzrd.in

  1. Download and install the module
  2. Browserify the module as a standalone bundle (single file)
  3. Send the processed module back to the requestor
  4. Store the file in the cache for faster serving on the future requests

#1 and #2 is performed only on the very first request on that module at that version. Unless you request your own module or those ‘nobody knows it exists’ modules, you get your bundled module in a couple of seconds.

So, here is what my **Scriptablify** module does when you require('module/*@version, autoupdate*/') in Scriptable scripts:

  1. Download the browserified module if needed.
    a. Check if module.js file exists in Scriptable/Documents folder.
    b. Check if autoupdate flag is on/off; boolean value as the second param to require() function.
    c. Check if the version installed satisfies the semver range of the version specified.
    d. Download the module if all condition combo determines to do so.

  2. Store the downloaded file to iCloud drive along with its version file.
    ** I wanted to indicate the version in the JS filename, but I won’t be able to get the current version from filename because I can’t read file with a wildcard.
    ** Then I wanted to give a module its own folder to better organize, but I was getting permission error even if I am creating a folder/file within Documents directory.

  3. Load the module from iCloud drive if everything goes well.

  4. Next time this module is used, it won’t make any http request unless you tell it to do so.

In short,
you can import and use any modules available on npm without worrying about downloading the file and put it in the disk by yourself.

There are some downsides, of course.

  1. Performance issue
    a. Since you are making http requests especially that transfers a file sometimes relatively large, it may slow down your script running time (only at the first trial)
    b. Once it’s been downloaded and you know you will keep that script, you can switch to importModule since you already have the module files stored by then.
    c. You’d probably want to make await requier() statements run concurrently if you are loading multiple modules in a single script.
  2. It will clutter your Documents folder.
    ** Two files per module, and one will show up in Scriptable as a script since it’s JS file. It will add up quickly if you use a number of modules.
    b. This is something I’d like to change asap. Please give me an advice on better FileManager usage.
  3. It might have many bugs :crazy_face::nauseated_face::face_vomiting:
    a. Again, I’m pretty new to Scriptable.
    b. I haven’t paid too much attention to the details and validation. I didn’t have much time I’m just sharing so that I get more idea to make it better.
    c. I haven’t done much end testing either. It may not work with certain modules.

Anyways, so here is the gist for the source.

Please let me know if you have any suggestions to make it better!

7 Likes

Very cool! Will require work without including your script in mine first somehow? I guess with importModule in every script that requires?

This should be built in to Scriptable to make it much more powerful than Shortcuts!

This so cool that I could not understand how to use this magic script ((
@hwangbible can you clarify how to use your example.js?

Hi, I was having the same trouble, you need to define require first (as the scriptablify at scriptable)


// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: gray; icon-glyph: magic;

// Defining function require
const require = importModule('scriptablify') // replace scriptablify with the name of the scriptable script

// Defaults to the latest version and no automatic update; if the file exists, just use it
const moment = await require('moment');

console.log(moment());
1 Like