Comparing JavaScript for Automation (JXA) and AppleScript

JavaScript for Automation (JXA) vs AppleScript

Original Edition 2020-09-16 18:50 GMT-5
(This is a wiki article, and I will update as new information becomes available.)

Let me start by saying that this can be a very emotional, hot topic for some people.
So I want to make it clear that everything I'm posting here is IMO (in my opinion), but based on the actual facts as best I know them.

I'll also say that my journey with and learning of both AppleScript and JavaScript for Automation (JXA) is still much in progress, particularly JXA.

Having said that, in theory, according to Apple, we should be able to address Mac automation objects/apps equally in both AppleScript and JXA. So the decision should come down to which language do you like best? Which syntax do you like best?

But that's just theory. In practice, there seem to be a few, and I think very few, automation areas where JXA doesn't seem to work correctly. IMO, these are edge cases, that most of us are unlikely to run into. In fact, I can't even name the areas.

For me, this is NBD (No Bid Deal), because if I happen run across one of these JXA problem areas, I'll just code that function using AppleScript, and then call it from JXA, using an AppleScript Library. It is interesting, and notable, that you can use an AppleScript Script Library from JXA, but not vice-versa.

One could probably draw up a list of pros/cons, but I'll just list the big advantages of each language:

AppleScript

  • Easier to Get Help
    • By comparison with JXA, there is a huge amount of scripts, script libraries, example scripts, and people knowledgeable about AppleScript.
    • So that means getting help is usually much easier with Mac automation
  • Developing and Debugging with Script Debugger 7+
    • The best available development tool for either AppleScript or JXA, but it only supports AppleScript
    • Provides a complete IDE, with excellent debugging, scripting dictionary support, and exploring of app object models
    • AFAIK, there is really nothing equivalent to it for JXA -- the Safari Debugger is the best JXA tool available today

JXA

  • Better programming language for programmers (again, IMO).
    • The "English-like" language of AppleScript just gets in the way for some (a lot?) of programmers
  • Extensive Native and Third Party Libraries
    • JXA is based on core JavaScript, and thus has a much better, more extensive, library of native functions than AppleScript
    • Because JavaScript has been used in web pages for decades, there is a multitude of script libraries, example scripts, and knowledgeable people for JavaScript
    • But this is all (or mostly) for using JavaScript in a Browser, NOT for using it for Mac automation
    • So, lots of help available for core JavaScript
    • But not much help for JXA, for Mac automation
  • Learning JXA
    * If you don’t know either AppleScript or JavaScript, then, IMO, JavaScript and JXA is easier to learn.
    * Of course, if you already know JavaScript, but not JXA, then learning how to use JXA to access app objects is all that you need to add to your skill set.

Support for Each Language

As I read back over what I just wrote, I realize that it might confusing concerning the support available for each language. The main issue here is with support for Mac automation.

  • AppleScript has the best support for Mac automation
  • JavaScript as the best support for core functions, and of course for Browser automation.

My Personal Experience and Views

For me, I'm in process of transitioning from AppleScript to JXA. Part of that is relearning much of the core JavaScript I knew years ago, but have forgot due to lack of use. My main motivation for moving to JXA is the language -- I love JavaScript, but I can barely tolerate AppleScript.

It is hard to tell what Apple is going to do. But I see JXA as being the future of Mac automation. Except for Script Libraries (which are used by both AppleScript and JXA), it seems that Apple is not putting much effort into improving AppleScript: maintaining, fixing bugs, improving documentation, or extending AppleScript.

I know that JXA is a disappointment to a few of the long-time AppleScript developers. It is hard to pin down how much of this is "sour grapes" vs real issues. I'm sure JXA could have been better -- almost anything can be improved -- but it seems good enough to me. As I said above, if I run into something I can't code in JXA, then I'll revert to AppleScript, and call it from JXA.

Bottom line

Both are good tools. Pick the one that works best for you.

The great thing is that KM gives you easy access to run both AppleScript and JXA scripts (and other script languages). So, you can choose either, or both, whatever works best for you. And as of KM 8.0.3+, we have access to the KM Editor scripting model, which is available to both AppleScript and JXA.

See Also:

  1. JavaScript vs. AppleScript now? - June 2016 KM Forum
  2. JXA Resources -- Nov 2019 @JMichaelTX
  3. Comparing JXA with AppleScript -- June 2016 KM Wiki (written largely by @ComplexPoint)
7 Likes

I was writing a simple script to make the front browser run some JavaScript, changing font attributes.

I was trying to make something like the below in AppleScript. I was hoping to group all Chromium-based browsers together. But I couldn't do it easily with AppleScript (see my comments in the script below):

tell application "System Events"
	set theBrowser to name of first application process whose frontmost is true
end tell

set jScript to "var x = document.querySelectorAll('.pull-left label'),i;for (i = 0; i < x.length; i++) { x[i].style.fontFamily = 'Times New Roman'; x[i].style.fontSize = '36px';}"

set chromiumJS to "tell application \"" & theBrowser & "\" to execute front window's active tab javascript " & jScript

if theBrowser is in {"Google Chrome", "Brave Browser", "Microsoft Edge"} then
        -- the command below does not work. It gives an error: "Expected end of line, etc. but found “front”."
	tell application activeApp to execute front window's active tab javascript jScript
        -- The command below works. 
        -- In other words, I have to explicitly state the browser name, such as "Brave Browser". 
        -- I can't use a variable, such as "activeApp" above.
	tell application "Brave Browser" to execute front window's active tab javascript jScript
else if activeApp is equal to "Safari" then
	tell application "Safari" to do JavaScript jScript in document 1
end if

JXA works very well:

var frontmost_app_name = Application('System Events').applicationProcesses.where({ frontmost: true }).name()[0];
var app = Application(frontmost_app_name);
var jScript = "var x = document.querySelectorAll('.pull-left label'),i;for (i = 0; i < x.length; i++) { x[i].style.fontFamily = 'Times New Roman'; x[i].style.fontSize = '36px';}"

if (frontmost_app_name == "Safari"){
	app.doJavaScript(jScript, {in: app.windows[0].currentTab()});
}else{ 
	app.windows[0].activeTab.execute({ javascript: jScript});
}

PS: I know with KM we can do this job very easily. But I was trying to turn the KM action into a script that non-KM users can also use.

3 Likes

Hi @JMichaelTX

I note your interest in JXA and your multiple contributions here, on Github, and elsewhere on the web showing your enthusiasm.

I have some projects in mind for which I agree JXA would be a really good fit. Sadly though it appears there is a substantial issue with JXA which makes it arguably unusable for Devonthink - and Devontech says there is nothing they can do since if Applescript works then JXA should work.

Any thoughts on why the Applescript version of this script works but not the Javascript version?

1 Like

DEVONtechnologies are unusual in resisting the equal status which Apple itself accords to the two osascript language interfaces (AS and JS)

In the case of their Smart Action code for example, they have never supported use any use of JS at all – only AppleScript.

JavaScript is interchangeable with AppleScript where a supplier's implementation of the osascript interface is standards-compliant, but my impression is that DT were probably stretching their implementation a little during the AppleScript-only (pre Sierra/Yosemite) stage, and either couldn't (or couldn't afford) to tidy it up and test it for compliant JavaScript use later on.

Various rationalisations / defences of this position have been presented on their forum over the years (occasionally descending to hagiographies of AS and deprecations of JS), but the bottom line is that they are an unusually JavaScript - resistant shop.

A pity – not least because a JSContext in their iOS and macOS versions of DEVONthink, like those provided by Omni, could yield some very useful cross-platform scriptability – and for me the AS-only and JS-flaky parts of their scripting interface are the main weakness of the macOS offering, and have pushed me away from reliance on DEVONthink towards custom scripting in the file-system.

2 Likes

Sorry, I can't be of much help here. I've only used DT Pro lightly, and have never tried to script it -- either with AppleScript or JXA.

Seems like @ComplexPoint has laid out the underlying issue very clearly.
It's really a shame that

Having said all that I would add two points:

  1. As @ComplexPoint has often mentioned, there can be real problems with the Script Editor
    • So, avoid using it if you can -- I now prefer VSCode
    • Always use a top-level function with the JXA and JavaScripts, regardless of the editor.
  2. You may be interested in a JXA template I have just posted:
    MACRO: [JXA] Template -- KM
2 Likes

A post was split to a new topic: Need Help with JXA Script Using Bookends

For the TextExpander application, you should also be able to use some variant of the code at the foot of this post:

1 Like

hi - can you please elaborate what you mean by this statement? Thank you.

As @ComplexPoint has pointed out many times if you do not put all of your JXA code in a function, then it gets mixed in with the global JavaScript environment.
And, Script Editor sometimes (always?) remembers the values of variables from the prior execution.

@ComplexPoint can explain all of this much better than I can.

So, here's a simple anonymous function that @ComplexPoint provided elsewhere:

(() => {
    'use strict';

    // Do everything in this purely local space, 
    // inside the definition of a nameless function, 
    // which is immediately executed because of the ( ) at the end.
    //Script Editor runs your JS in a global environment which persists between script runs.

})(); 

For the JXA template I use, see MACRO: [JXA] Template -- KM

3 Likes

That's right – JavaScript interpreter environments, like Script Editor, and the JavaScript console in browsers, generally have a persistent global name space, containing a whole lot of definitions. It's a crowded space.

You can see its contents by running the following one-word JS script in Script Editor:

this

(You will see a lot of crowded detail appearing in the Script Editor Results panel)

If you don't write your code definitions inside a function wrapping, they are just added to this already cluttered global namespace, where they stay, even after the script has run, creating hidden pollution and sources of confusion downstream.

If we try to skip an outer function-wrapping, and run the following bare script in Script Editor, it will work the first time, but fail if we hit the Run button again:

const n = 2;

n + n;

If, however, we enclose our code in an immediately invoked function expression (IIFE), then:

  • it has a private and temporary name-space of its own, between { the braces },
  • the global name-space is protected from pollution,
  • and the script succeeds with every re-run:
(() => {
    "use strict";

    const n = 2;

    return n + n;
})();

Breaking it down, an expression like:

() => 2

defines an un-named function which just returns a value,

adding braces gives it its own name-space, in which we can use const and let to bind names to values:

() => {
    const n = 2;

    return n + n;
}

Adding "use strict"; switches on more helpful error messages if things go wrong:

() => {
    "use strict";

    const n = 2;

    return n + n;
}

and wrapping it in parentheses, with a final () for invocation/running

turns it into a function expression which is "immediately invoked".

(without that, it will just be a function definition which isn't actually run)

IIFE:

(() => {
    "use strict";

    const n = 2;

    return n + n;
})();

As an alternative to an IIFE, which works in any JS interpreter, the special environment of osascript and "Script Editor" also pre-defines the special name run, which you can use as an alternative top-level function wrapping.

function run() {
    "use strict";

    const n = 2;

    return n + n;
};

This run function approach won't, however, work in other scripting contexts, like OmniGroup omniJS, the TaskPaper JSContext, Drafts, Scriptable, or web scripting.

(The advantage of an IIFE is that it's available everywhere).

I personally have an empty IIFE bound to a shortcut for quick pasting.

13 Likes

Getting a sense, from KM, of how busy the global name-space of a JS interpreter is:

image

4 Likes

That’s an outstanding exposition for us novices, @ComplexPoint . Thank you!

2 Likes

Indeed. I was a little delayed in being able the replies, and as I was reading it, I was thankful at every step of the description of the (() => {})(). So thank you @ComplexPoint for the explanation. And @JMichaelTX thanks as always for the patient explanation.

4 Likes