JavaScript Support: Difference between revisions

From Kolmafia
Jump to navigation Jump to search
→‎Basics: Add "Library Functions" and "Importing and Exporting" subsections
Add "History" section
Line 1: Line 1:
As of revision 20509, KoLmafia supports scripting in JavaScript! You can run JS code from the CLI using <code>js <nowiki><code></nowiki></code>, and you can call scripts through any of the normal methods. Consult and "lifecycle" scripts (e.g. <code>betweenBattleScript</code>) are supported as well as of revision 20519. '''This support is still experimental - you have been warned.'''
As of revision 20509, KoLmafia supports scripting in JavaScript! You can run JS code from the CLI using <code>js <nowiki><code></nowiki></code>, and you can call scripts through any of the normal methods. Consult and "lifecycle" scripts (e.g. <code>betweenBattleScript</code>) are supported, too! '''This support is still experimental - you have been warned.'''


== Basics ==
== Basics ==
Line 38: Line 38:
   > js print("My name is " + myName())
   > js print("My name is " + myName())
  My name is <playername>
  My name is <playername>
Note: All library functions that return a {{type|boolean}} will throw an exception instead of returning <code>false</code>.


=== Importing and Exporting ===
=== Importing and Exporting ===
Line 84: Line 82:
Enumerated objects support the <code>toString()</code> method, which acts like the {{f|to_string}} ASH function.
Enumerated objects support the <code>toString()</code> method, which acts like the {{f|to_string}} ASH function.


Note: As of r20620, KoLmafia no longer provides a custom <code>valueOf()</code> method for enumerated objects. This means that you can no longer retrieve the ID of an item, effect, or familiar using <code>Number()</code>. Instead, use the {{f|to_int|toInt}} library function.
To retrieve the numeric ID of enumerated objects, use the {{f|to_int|toInt}} library function:


Example:
<syntaxhighlight lang="js">
<syntaxhighlight lang="js">
let item = Item.get("filthy lucre");
let item = Item.get("filthy lucre");
Line 189: Line 186:


If you use a bundler such as [https://webpack.js.org/ Webpack] or [https://rollupjs.org/ Rollup], you should change the <code>module</code> to <kbd>"ES2015"</kbd>, and let the bundlers convert your code to CommonJS.
If you use a bundler such as [https://webpack.js.org/ Webpack] or [https://rollupjs.org/ Rollup], you should change the <code>module</code> to <kbd>"ES2015"</kbd>, and let the bundlers convert your code to CommonJS.
== History ==
* [https://kolmafia.us/threads/20520-this-is-a-big-patch-to-set-up-consult-scripts-lifecycle-scripts-choiceadventurescript.25660/ r20520]: Lifecycle scripts now support JavaScript.
* [https://kolmafia.us/threads/20620-remove-valueof-as-per-philmasterplus.25845/ r20620]: Enumerated objects no longer provide a custom <code>valueOf()</code> method. Use {{f|to_int|toInt}} instead.
* [https://kolmafia.us/threads/20780-javascript-runtime-no-longer-interrupts-when-library-functions-return-false.26174/ r20780]: When calling ASH runtime library functions from JavaScript, functions that return a {{type|boolean}} will return <code>false</code> on failure instead of throwing a string as an exception.

Revision as of 12:04, 2 July 2021

As of revision 20509, KoLmafia supports scripting in JavaScript! You can run JS code from the CLI using js <code>, and you can call scripts through any of the normal methods. Consult and "lifecycle" scripts (e.g. betweenBattleScript) are supported, too! This support is still experimental - you have been warned.

Basics

Library Functions

All functions in the ASH runtime library are available inside the built-in kolmafia module. To use these functions, you must use the require() function to import them:

const { print, myName() } = require("kolmafia");
print("Hello, " + myName());

// Alternative
const kolmafia = require("kolmafia");
kolmafia.print("Hello, " + kolmafia.myName());

The require() function is usually called once at the top of the script.

Names of ASH functions are converted to camelCase. For example, print_html() in ASH becomes printHtml() in JavaScript:

ASH JavaScript
print_html("<b>Some text</b>");
const { printHtml } = require("kolmafia");
print("<b>Some text</b>");

When directly executing inline JavaScript code with the js or jsq commands, all library functions are already imported for you, so you don't have to import them:

 > js print("My name is " + myName())
My name is <playername>

Importing and Exporting

KoLmafia uses the CommonJS module system, similar to Node.js. You can export functions and values from one JavaScript file and import them from another JavaScript file:

source.js runner.js
const { print } = require("kolmafia");
module.exports.hello = function hello() {
  print("Hello, world!");
};
const { hello } = require("./source.js");
hello();

Check out this guide for more examples.

You can export a function named main() to run it only when the script is invoked directly. When your script is imported by another script, the main() function will be ignored.

You can also import ASH scripts from JavaScript code. For details, see ASH and JavaScript Interoperability.

Data Type Classes

All enumerated data types in ASH are available as classes in JavaScript. For example, monster is available as the Monster class, and item is available as the Item class.

Each enumerated class has the following static methods:

  • <ClassName>.get() takes a number or string and returns an object of the class.
    • For example, Monster.get("fiendish can of asparagus") is equivalent to $monster[ fiendish can of asparagus ] in ASH. Item.get(1) is equivalent to $item[ 1 ].
    • This also accepts string representations of integers. For example, Item.get(5) and Item.get("5") return the same result.
  • <ClassName>.get() can also take an array of numbers and strings, and return an array of objects.
    • For example, Item.get(["seal-clubbing club", "pail", 5]) is similar to $items[ seal-clubbing club, pail, 5 ] in ASH. However, the JavaScript version returns an array of objects, instead of a boolean map.
    • Passing an empty array returns an empty array (i.e. Item.get([]) is not the same as $items[]).
  • <ClassName>.all() takes no argument and returns all possible values of the class.
    • For example, Monster.all() returns an array of all known monsters. This is similar to $monsters[] in ASH.

Enumerated objects support the toString() method, which acts like the to_string() ASH function.

To retrieve the numeric ID of enumerated objects, use the toInt() library function:

let item = Item.get("filthy lucre");
let itemId = toInt(item);

Other

  • ASH maps are converted to plain JS objects, and ASH arrays are converted to JS arrays.
  • You can look at the type reference for the JS version of the ASH runtime library with jsref, which works just like ashref.

ASH and JavaScript Interoperability

JavaScript scripts can require() ASH scripts and use their functions. When an ASH script is require()-ed by JavaScript code, KoLmafia will execute top-level code, but only export functions in the top-level scope. ASH variables are not exported.

For example, you can use Zlib if it is installed:

const { getvar } = require("zlib.ash");
let myvar = getvar("SOME_VAR_NAME");

ASH scripts cannot import JavaScript scripts.

JavaScript Version and Features

KoLmafia uses the Rhino engine to execute JavaScript code. Rhino supports an older version of JavaScript called "ES5", plus some features from newer versions. This means that many JavaScript features that work in web browsers might not work in KoLmafia.

Here is an incomplete list of post-ES5 features supported by Rhino (source):

Supported

  • Syntax
    • let and (partially) const
      • Does not support block-level scoping or temporal dead zones, meaning that you cannot use const for loop variables. for (const a in obj) { ... } is a syntax error.
    • Array/object destructuring (but spread/rest syntax (...) is not supported)
    • for...of loop
    • Arrow functions: () => {}
    • Octal and binary literals
  • Features
    • Symbol
    • Set, Map, WeakSet, WeakMap
    • ES2015 methods in Array, Math, Number, Object, String
    • Array.prototype.includes()
    • String.prototype.padStart()/padEnd()/trimStart()/trimEnd()
    • TypedArray: Can be constructed, but most TypedArray-specific methods are unavailable.

Unsupported

  • Syntax
    • Spread/rest syntax (...)
    • Template string literals: Backtick string literals (``) are not a syntax error, but are treated as plain string literals.
    • Classes
    • ECMAScript modules (import/export)
    • Default function parameters
    • Computed property names
    • Async/Await
    • Trailing commas in function definitions (oddly, trailing commas are supported in function calls)
  • Features
    • Promise
    • Proxy
    • Reflect

Other

Most JavaScript globals available in browsers and/or server-side environments like Node.js are not available. This includes alert(), console.log(), and setTimeout().

Transpiling

Folks with experience doing JavaScript web development are likely well-acquainted with tools such as Babel, Webpack, and TypeScript. These tools allow you to write modern JavaScript code. and can transpile the code down to an older version of JS supported by a particular engine. That approach works well with Rhino.

  • Babel: As of r20558, you will still need to apply several patches to Babel in order to get babel-preset-env to work. See [1] for an example of a working Babel/Webpack/Typescript configuration; you'll need the configuration files and also the patches, which can be applied with patch-package.

TypeScript

If you use TypeScript without Babel, you can set the target to "ES5".

TypeScript does not provide polyfills for modern JavaScript APIs missing in Rhino. To use them, you must supply your own.

To avoid accidentally using any missing APIs, we recommend using the following configuration for your tsconfig.json:

{
  "compilerOptions": {
    // JavaScript APIs supported by Rhino 1.7.13
    // See https://mozilla.github.io/rhino/compat/engines.html for more info
    "lib": [
      "ES5",
      "ES2015.Collection",
      "ES2015.Core",
      "ES2015.Generator",
      "ES2015.Iterable",
      "ES2015.Symbol",
      "ES2015.Symbol.WellKnown",
      "ES2016.Array.Include",
      "ES2017.String",
      "ES2019.String"
    ],
    // Rhino uses require() instead of import/export
    // Note: If you use Webpack or Rollup, change this to "ES2015"
    "module": "CommonJS",
    // Rhino supports ES5+
    // Note: If you use Babel with TypeScript, change this to "ESNext"
    "target": "ES5"
  }
}

If you use a bundler such as Webpack or Rollup, you should change the module to "ES2015", and let the bundlers convert your code to CommonJS.

History

  • r20520: Lifecycle scripts now support JavaScript.
  • r20620: Enumerated objects no longer provide a custom valueOf() method. Use toInt() instead.
  • r20780: When calling ASH runtime library functions from JavaScript, functions that return a boolean will return false on failure instead of throwing a string as an exception.