[Tip] Running JavaScript in Shortcuts (iOS, macOS)

Changes from previous post on Reddit: Corrected speed comparison of rendering methods, and added info on critical iOS 17 bug.

For data processing that involves a large number of operations and/or loops, JavaScript can be much faster than built-in shortcut actions. Running JS using the data URI scheme (https://en.wikipedia.org/wiki/Data_URI_scheme) has been a long-known trick, so I’ll try to summarize the correct way to do it.

The trick works because Shortcuts runs embedded JS when it tries to render an HTML page. Callbacks or async functions are not supported (unless they finish within < 0.2 sec). For networking, XMLHttpRequest works on non-iOS 15.

JavaScript Code

Provide your JS code inside the <script> tags along with the data:text/html;charset=utf-8, prefix in a URL action:

No other tags are required such as <html> or <meta>, and the code part doesn’t have to be encoded in base64 (except for some cases on iOS 17; refer below). The charset part is necessary to handle Unicode characters.

Input

Inserting variables directly into JS code is possible, but only if you know it’s safe to do so:

This can fail if the text contains characters such as " or \ or strings like </script>, or if the number is Eastern Arabic like ١٢٣ or contains the decimal comma like 0,023. Therefore, it is best to pass input variables in a dictionary using the “Set Dictionary Value” or “Dictionary” action:

Output

Output should be written onto the web page using document.body.textContent = output. To prevent output alterations (e.g. changing &lt; to <, a new line or multiple spaces into a single space, losing <tag-like> strings), it is safer to URL-encode it using encodeURIComponent(). And again, to pass values correctly in various regions, it is better to produce the output as a JSON object and stringify it using JSON.stringify():

Note: <script>document.write(output)</script> can be used instead of <body/><script>document.body.textContent = output</script> but it uses more memory space.

HTML Rendering

Since Shortcuts treats web pages as Rich Text, the final output from a rendered page can be converted to text easily. After that, the URL-encoded output has to be decoded back. There are four known ways to produce rich text output (then URL-decode it) from a data URI:

  • Get Contents of Web Page

  • Get File of Type com.apple.webarchive

  • Get File of Type public.rtf

  • URL as Rich Text

They generate the same result at about the same speed, but the last one consumes less memory because it has one less action. The speed comparisons in the previous post were totally wrong; they are equally fast.

Sad Story about iOS 17

iOS 17 has introduced a new bug.

A data URI will output blank Rich Text if it contains the # or :hash: symbol (the number sign, pound sign, hash, hashtag, crosshatch, octothorpe, or whatever).

This affects any JS code that contains the symbol. There are two ways to conceal them from the URI:

  • In JS code, fortunately, # and :hash: symbols most likely appear within string literals; in this case, you can replace \u0023 with \\u0023

  • If they appear elsewhere such as in html or css, the entire data needs to be base64-encoded. This is much slower than Replace Text. Please note that a base64 data URI split into every 76 characters runs faster than the same base64 URI in a single line.

Conclusion

Finally, here’s a sample shortcut that combines all of the above:

https://www.icloud.com/shortcuts/536ee65ed134405a89d57ec187299d7a

6 Likes

Updated the bug section and the sample shortcut because the data URI bug occurs with the :hash: emoji as well as the # symbol. The proper workaround is to replace \u0023 with \\u0023 with regex on.

1 Like