Just spent the last 2 hours trying to figure out how to run javascript that can use external JS modules while having access to BTT's API.
I can achieve one or the other using a different execution approach but can't seem to figure out a simple way to do both in a single action. Please hear me out...
Run Real Javascript **action:**
While this action provides BTT's scripting API (trigger_named(), get_selection(), ...), I can't require or import external modules.
Execute Shell **action:**
When pointing the launch path to node, I can successfully execute a JS module (that may then import other external modules) but these are ran externally from BTT so I can't call BTT's functions.
The only workaround I can think of is to enable the BTT socket server for CLI to be able to call BTT from external scripts, but this is becoming a complicated solution for what I'm trying to achieve...
I've got a bunch of existing modules that I use to parse/transform text, run things on my computer and also a bunch of 3rd party modules from NPM that I wish to integrate into my workflow. Combining all of these in a single file to be called directly from BTT is a non-sense, but running them externally isolate them from BTT's interface...
Is there some feature that I'm missing to close that gap?
Thanks for any help!
Run Real JavaScript does not have a node/bun runtime, so it can not use require or import. You can however load self contained pure JS files.
You could use run_shell_script from BTT's Run Real JavaScript and call your node script from there, then react to the return value via BTT.
Alternatively here is the work from @Worie that allows you to talk to the BTT webserver from NodeJS. Maybe I could bring that up to date with the latest BTT scripting additions using AI - I'll give that a shot later. I think the basics still work as is:
not at all, the webserver is still supported. It might even be faster than calling the CLI from NodeJS - however I haven't done benchmarking on that yet.
I'll look into that in more detail later, maybe having an official JS library (either based on CLI or Webserver) for external use in NodeJS or similar makes sense now (with AI help I should be able to support that because generating interfaces etc. is basically free now)
I have now published an ai auto generated JS package that works either via http (webserver) or via the unix socket (cli) or can even be used within BTT's Run Real JavaScript. It is available via NPM
npm i bettertouchtool
Docs & code
I'll add it to BTT's build process so it continues to be updated.
Wow this is impressive! I'll certainly look into that.
Yesterday I was thinking of setting up a workflow that allows me to quickly combine JS modules into a single file to which I could point BTT's Run Real Javascript action to. But with v.6.735 this won't be necessary anymore.
Just out of curiosity, how much time did you spend to generate the bettertouchtool package? Did you start from scratch yesterday??
mostly - but I did a lot of work to make these things possible during the last year. It is just a thin wrapper around the BTT interface and these things are really super easy to generate and maintain via AI once they have enough information.
Also the AI could use @Worie's code as inspiration even if it didn't reuse code that helped a lot.
I just tried using require() from within a Run Real Javascript action and encountered 2 issues:
Placing a CommonJS module within ~/Library/Application Support/BetterTouchTool/JavaScriptModules and requiring it only by its name throws an error that the module doesn't exist. But if I provide the whole path it works.
Once a module has been required, changes brought to that module aren't taken into account unless I restart BTT. I guess this makes sense from a performance point of view but when developping external modules it quickly becomes an issue. Could you include a JS function or implement a "dev mode: force reload" checkbox in the Run Real Javascript action that would force BTT to reload external JS modules that changed?
Good points! Should be resolved in BTT 6.742 (uploading)
require("name") from ~/Library/Application Support/BetterTouchTool/JavaScriptModules now also resolves package folders (name/package.json → main/exports, or name/index.js) and nested names like utils/parse, with or without .js/.cjs. Previously only name.js, name.cjs and name/index.js worked. I suspect your module was a folder with a package.json, is that possible?
Modules are now reloaded automatically when the file on disk changes (BTT compares the modification date on every require), so editing a module takes effect on the next run without restarting. You can also force it with require.reload("name") / require.reload() or delete require.cache["name"].
So far so good! Both issues seem to be resolved in 6.742!
From my undestanding, the fact that BTT keeps the external JS modules (require()) "alive" means that once loaded the code may run very quickly, right? If, for instance, I lauch a named trigger every time I press the CMD key and that trigger runs a Run Real Javascript action that requires a bunch of external scripts, the loading happens just once an then it's just a matter of execution?