Use your favorite node.js modules, or your own local modules, in your JXA (OSX Javascript automation) scripts. Based on this awesome tip from the JXA Cookbook.
It's OS X automation, using Javascript.
Applescript has long been the scripting language provided by Apple for automating and integrating apps on Mac OS X. The Internet is replete with Q & A, tips and examples of how to use Applescript to automate various menial tasks, like:
- Archiving anything to Evernote
- Capturing an email or Evernote link to your task manager (e.g. OmniFocus)
- Capture your Safari reading list to your task manager
Since OS X 10.10 (Yosemite), Javascript (called Javascript for Automation, or JXA) is also supported for app automation. This is great news, especially for Javascript developers who want to automate workflows on their Mac.
Based on this awesome tip, node-jxa
allows you to use commonJS modules in your JXA scripts. You can require
modules installed from npm, or your own local modules:
const _ = require( 'lodash' ); // from npm
const myModule = require( './my-module' ); // local modules too
ES6 module syntax (
import from
) isn't currently supported, so stick withrequire()
, and usemodule.exports
in your own local modules.
You can also use your favorite Javascript editor instead of the OS X Script Editor, and use workflow that is much more familiar to JS developers (vs. compiling .js files to .scpt binary format).
So long as your editor can launch a shebang'd script, you can run or debug JXA while you edit.
You'll likely want to install node-jxa globally:
yarn global add node-jxa # or the npm equivalent: npm install -g node-jxa
This will install node-jxa
and make it available in your PATH
env var.
.. then you can (optionally) use a shebang at the top of your JXA script:
#! /usr/bin/env node-jxa
// ... rest of script
and make your script executable:
chmod u+x my-jxa-script.js
.. so you can run it from the command line (or, using your favorite script launcher, keyboard shortcuts, etc).
./my-jxa-script.js
You can of course use the node-jxa
command on the command line; simply provide the jxa script as the first argument:
node-jxa ./my-jxa-script.js
Note that your installed node.js engine is only used by node-jxa
to bundle up your module dependencies, and to decorate your script with a couple of needed additions (using the Browserify API). The resulting code is then piped to osascript
, and your OS X JavaScriptCore (modified for OSA integration) is used to execute it. Node.js is not ultimately used to execute your script.
Fortunately the supported ES syntax and features is quite modern (depending on your OS X version). As long as your installed node.js engine is recent enough for Browserify to bundle your code, you should be able to use just about any modern ES syntax you like (except for ES6 module syntax, i.e. import from
). To see what's supported, check what version of Safari you have, then find its column at the Kangax compatibility site.
Note: the Kangax table has a column for JXA, but it doesn't appear to be current.
All modules you require
in your scripts must be installed (i.e. in the node_modules
dir) to be available, so Browserify can bundle them into your script.
I suggest managing your node-jxa scripts like any node.js project, with a package.json specifying the needed module dependencies. Simply use yarn
or npm
to add and remove the libraries you need.
Note that Browserify won't automatically package modules for which the
require
d path is computed at runtime. For example,let myNeededModulePath = './my/needed/module' ; let myModule = require( myNeededModulePath );
will leave the module out and it won't work. This can happen within modules you're getting from npm. Browserify has techniques for handling this, but node-jxa doesn't currently employ them.
You can use your Applescript libraries in your JXA scripts using the Library
global function, like so:
let myAsLib = Library( 'myApplescriptLibary' ) // skip the .scpt suffix
All top-level Applescript routines and handlers in the library will be available as functions on the imported object.
Library files must be located in your Script Libraries folder or in your OSA_LIBRARY_PATH (the latter as of OS X 10.11) to be used in this way.
This works with JXA libraries too - you can use Library()
to import .scpt scripts compiled from Javascript. But since node-jxa allows you to require()
from your local js files, it's much better to use your js libraries like local node.js modules, using module.exports
.
There are several other globals added by the JXA runtime; they are detailed by Apple, here.
With node-jxa you can use your favorite JS editor for writing and managing your JXA code, but the OS X Script is still useful for viewing the Scripting Dictionaries for your scriptable apps. Essentially, this is the documentation for the API exposed by the apps' developers. Look for Open Dictionary... on the File Menu, and be sure to select JavaScript
in the language selector at the top.
Some things are weird, including but not limited to:
- trying to
console.log
a JXA application object will probably crash the process. - Element Arrays (arrays provided directly by Applications ( or filtering via
whose()
) are weird. A simple trick likearray.map( el => el )
will give you a real JS array. - property access for JXA objects is expensive. If you're doing many reads, your script can take a long time and may even time out. A caching strategy can help.
You can debug your JXA scripts using Safari dev tools. To debug, enable JSContexts support in Safari, then simply include the debugger;
command in your script. When you run the script it'll stop and open at that spot in the Safari debugger (from here, you can add additional breakpoints in Safari's debugger).
If you add the --debug
(or -d
) switch to the node-jxa
, command (including in your shebangs), Browserify will include sourcemaps in the bundled code, for a cleaner debugging experience that is more focused on your own code.
Sourcemaps will also be included if the environment variable NODE_DEBUG_JXA
is set to true
or 1
, regardless of whether the --debug
or -d
switches are used.
Depending on your Mac, you might never notice a difference in performance when including sourcemaps. You'll definitely see more of a performance hit from requiring many large or spurious libraries, regardless of sourcemaps.
- The JXA Cookbook
- From Apple:
- A Beginners Guide to JXA, JavaScript Application Scripting
- Example OS X applications written in JavaScript and Building OS X Apps with JavaScript by Tyler Gaw
- Automation for OS X: the JavaScript way – HackMag
- lucaswoj/slick: A port of React Native to OS X using the "JavaScript for Automation" framework
Note that there are many more resources available for Applescript on the interwebs (or even from Apple) than there is for JXA. When googling for an example or recipe, you'll probably find much more help by looking an Applescript that does what you want, and then converting to Javascript yourself. You can temporarily use an Applescript library if the conversion eludes you for a time.