Upgrading From Nashorn: Difference between revisions
m (Taustin moved page upgrading From Nashorn to Upgrading From Nashorn without leaving a redirect) |
|||
(5 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
{{Languages| | {{Languages|javascript}} | ||
'''Note this <u>only</u> affects {{code|js.eval*}} functions, not the HTML5 javascript engine.''' | |||
If you used {{func|js.eval}} prior to Maptool 1.8.x, it used the old Nashorn Javascript engine. Starting with 1.10.0, Maptool now uses Graalvm's Javascript engine. | If you used {{func|js.eval}} prior to Maptool 1.8.x, it used the old Nashorn Javascript engine. Starting with 1.10.0, Maptool now uses Graalvm's Javascript engine. | ||
This has a few incompatibilities with Nashorn, so since existing scripts will need some attention anyway, now is the best time to make other incompatible changes to the Javascript API. | This has a few incompatibilities with Nashorn, so since existing scripts will need some attention anyway, now is the best time to make other incompatible changes to the Javascript API. | ||
Below is a basic guide on updating existing {{func|js.eval}} code. | Below is a basic guide on updating existing {{func|js.eval}} code. | ||
Line 11: | Line 13: | ||
{{code|atob}} and {{code|btoa}} are not provided by graalvmjs. They can easily be reimplemented via the following functions | {{code|atob}} and {{code|btoa}} are not provided by graalvmjs. They can easily be reimplemented via the following functions | ||
< | <syntaxhighlight lang="js"> | ||
function atob(s) { | function atob(s) { | ||
MTScript.setVariable("toDecode", s) | MTScript.setVariable("toDecode", s) | ||
Line 21: | Line 23: | ||
return MTScript.evalMacro("[r: btoa(toEncode)]") | return MTScript.evalMacro("[r: btoa(toEncode)]") | ||
} | } | ||
</ | </syntaxhighlight> | ||
==load== | ==load== | ||
{{code|load()}} is likewise gone. While it is possible to re-implement it over top of {{func|REST.get}}, that is discouraged. Instead, external scripts can be moved into library tokens and accessed via the new Library Token URI Access protocol. | {{code|load()}} is likewise gone. While it is possible to re-implement it over top of {{func|REST.get}}, that is discouraged. Instead, external scripts can be moved into library tokens and accessed via the new Library Token URI Access protocol. | ||
< | <syntaxhighlight lang="mtscript"> | ||
[r: js.eval("load('http://example.com/something.js')")] | [r: js.eval("load('http://example.com/something.js')")] | ||
</ | </syntaxhighlight> | ||
becomes | becomes | ||
< | <syntaxhighlight lang="mtscript"> | ||
[r: js.evalURI("name", "lib://example/macro/something.js")] | [r: js.evalURI("name", "lib://example/macro/something.js")] | ||
</ | </syntaxhighlight> | ||
==Trusted Namespaces== | ==Trusted Namespaces== | ||
Line 41: | Line 43: | ||
If you want to register a javascript UDF, and restrict it, or alter its behavior based on the if the calling context is trusted, use | If you want to register a javascript UDF, and restrict it, or alter its behavior based on the if the calling context is trusted, use | ||
< | <syntaxhighlight lang="javascript"> | ||
if (MTScript.evalMacro('[r: isTrusted()]')) { | if (MTScript.evalMacro('[r: isTrusted()]')) { | ||
//protected code here. | //protected code here. | ||
} | } | ||
</ | </syntaxhighlight> | ||
==js.eval== | ==js.eval== | ||
Line 53: | Line 55: | ||
The simplest replacement is {{func|js.evalNS}}, which takes the namespace to use as its first parameter. Note that the return semantics are also slightly different from {{func|js.eval}}, as it ''does not'' wrap its code fragment in an anonymous function, and uses tail-return to return a value (the statement in [[https://en.wikipedia.org/wiki/Tail_call#Syntactic_form|Tail Position]] is returned). Also, the magic {{code|args}} parameter from {{func|js.eval}} is gone. Instead, [[js:MTScript|JS:MTScript.getCallingArgs]] returns the argument list as a javascript array. | The simplest replacement is {{func|js.evalNS}}, which takes the namespace to use as its first parameter. Note that the return semantics are also slightly different from {{func|js.eval}}, as it ''does not'' wrap its code fragment in an anonymous function, and uses tail-return to return a value (the statement in [[https://en.wikipedia.org/wiki/Tail_call#Syntactic_form|Tail Position]] is returned). Also, the magic {{code|args}} parameter from {{func|js.eval}} is gone. Instead, [[js:MTScript|JS:MTScript.getCallingArgs]] returns the argument list as a javascript array. | ||
< | <syntaxhighlight lang="mtscript"> | ||
[r: js.eval("var a = [1,2,3]; return a[1] + args[0]", 5)] | [r: js.eval("var a = [1,2,3]; return a[1] + args[0]", 5)] | ||
</ | </syntaxhighlight> | ||
becomes | becomes | ||
< | <syntaxhighlight lang="mtscript"> | ||
[r: js.evalNS("demo", "var a = [1,2,3]; a[1] + (MTScript.getCallingArgs()[0]|0)", 5)] | [r: js.evalNS("demo", "var a = [1,2,3]; a[1] + (MTScript.getCallingArgs()[0]|0)", 5)] | ||
</ | </syntaxhighlight> | ||
Note the {{code|\|0}} converts the argument to an integer. | Note the {{code|\|0}} converts the argument to an integer. | ||
Line 68: | Line 70: | ||
It is best to keep related javascript code in isolated namespaces. To avoid namespace collisions, use a good, unique string for your namespace. This is not as cumbersome as it might first appear, because you can use a variable to hold the name for repeated calls, and because new code is unlikely to make many {{func|js.evalNS}} calls. | It is best to keep related javascript code in isolated namespaces. To avoid namespace collisions, use a good, unique string for your namespace. This is not as cumbersome as it might first appear, because you can use a variable to hold the name for repeated calls, and because new code is unlikely to make many {{func|js.evalNS}} calls. | ||
< | <syntaxhighlight lang="mtscript"> | ||
[h: myNS = "com.example.maptool.myLibrary"] | [h: myNS = "com.example.maptool.myLibrary"] | ||
[r: js.evalNS(myNS, "function add(a,b) { return a+b; }")] | [r: js.evalNS(myNS, "function add(a,b) { return a+b; }")] | ||
</ | </syntaxhighlight> | ||
==Javascript UDFs== | ==Javascript UDFs== | ||
As mentioned in the previous section, using {{code|js.eval*}} directly is mostly not necessary anymore. Instead, put the javascript code in a Lib:Token with URI access enabled, and then take the entry points and expose them as javascript UDFs. | As mentioned in the previous section, using {{code|js.eval*}} directly is mostly not necessary anymore. Instead, put the javascript code in a Lib:Token with URI access enabled, and then take the entry points and expose them as javascript UDFs. | ||
< | <syntaxhighlight lang="mtscript"> | ||
[h: js.evalURI(myNS, "lib://myLibrary/macro/myFirstLib.js")] | [h: js.evalURI(myNS, "lib://myLibrary/macro/myFirstLib.js")] | ||
[h: js.evalURI(myNS, "lib://myLibrary/macro/mySecondLib.js")] | [h: js.evalURI(myNS, "lib://myLibrary/macro/mySecondLib.js")] | ||
</ | </syntaxhighlight> | ||
If the scripts don't interact, change the namespace used and they cannot see each other. | If the scripts don't interact, change the namespace used and they cannot see each other. |
Latest revision as of 23:59, 3 May 2023
Languages: English
Note this only affects js.eval*
functions, not the HTML5 javascript engine.
If you used js.eval() prior to Maptool 1.8.x, it used the old Nashorn Javascript engine. Starting with 1.10.0, Maptool now uses Graalvm's Javascript engine.
This has a few incompatibilities with Nashorn, so since existing scripts will need some attention anyway, now is the best time to make other incompatible changes to the Javascript API.
Below is a basic guide on updating existing js.eval() code.
atob and btoa
atob
and btoa
are not provided by graalvmjs. They can easily be reimplemented via the following functions
function atob(s) {
MTScript.setVariable("toDecode", s)
return MTScript.evalMacro("[r: atob(toDecode)]")
}
function btoa(s) {
MTScript.setVariable("toEncode", s)
return MTScript.evalMacro("[r: btoa(toEncode)]")
}
load
load()
is likewise gone. While it is possible to re-implement it over top of REST.get(), that is discouraged. Instead, external scripts can be moved into library tokens and accessed via the new Library Token URI Access protocol.
[r: js.eval("load('http://example.com/something.js')")]
becomes
[r: js.evalURI("name", "lib://example/macro/something.js")]
Trusted Namespaces
One of the new features possible with graalvmjs is the ability to have multiple Javascript contexts (called namespaces to avoid confusion with MTScript contexts, and because they are named). These namespaces are manipulated through js.createNS() and js.removeNS(), or implicitly created the first time they are accessed. Additionally, these namespaces can be trusted (if made from a trusted MTScript context), or not trusted (if made by an untrusted context, or explicitly created with the js.createNS() and set to untrusted). Any player can run javascript in an untrusted namespace, but MTScript macros run from within an untrusted namespace are untrusted, and some javascript functions behave differently in untrusted namespaces.
If you want to register a javascript UDF, and restrict it, or alter its behavior based on the if the calling context is trusted, use
if (MTScript.evalMacro('[r: isTrusted()]')) {
//protected code here.
}
js.eval
js.eval() is now always untrusted. Additionally, it creates a new anonymous namespace for each run. This is normally fine, as internally it wraps the code fragment in an anonymous function, but if you have code which escapes the anonymous function to persist values, it will need changing.
The simplest replacement is js.evalNS(), which takes the namespace to use as its first parameter. Note that the return semantics are also slightly different from js.eval(), as it does not wrap its code fragment in an anonymous function, and uses tail-return to return a value (the statement in [Position] is returned). Also, the magic args
parameter from js.eval() is gone. Instead, JS:MTScript.getCallingArgs returns the argument list as a javascript array.
[r: js.eval("var a = [1,2,3]; return a[1] + args[0]", 5)]
becomes
[r: js.evalNS("demo", "var a = [1,2,3]; a[1] + (MTScript.getCallingArgs()[0]|0)", 5)]
Note the \
converts the argument to an integer.
Also note that subsequent calls to the same namespace will have whatever variables were left previously still scattered around. This means if you use {{{1}}}
at the top level, repeated invocations of that will fail, as let
doesn't allow you to declare a variable which already exists. You can avoid this by using your own anonymous function, or by calling js.removeNS() between invocations.
Namespaces
It is best to keep related javascript code in isolated namespaces. To avoid namespace collisions, use a good, unique string for your namespace. This is not as cumbersome as it might first appear, because you can use a variable to hold the name for repeated calls, and because new code is unlikely to make many js.evalNS() calls.
[h: myNS = "com.example.maptool.myLibrary"]
[r: js.evalNS(myNS, "function add(a,b) { return a+b; }")]
Javascript UDFs
As mentioned in the previous section, using js.eval*
directly is mostly not necessary anymore. Instead, put the javascript code in a Lib:Token with URI access enabled, and then take the entry points and expose them as javascript UDFs.
[h: js.evalURI(myNS, "lib://myLibrary/macro/myFirstLib.js")]
[h: js.evalURI(myNS, "lib://myLibrary/macro/mySecondLib.js")]
If the scripts don't interact, change the namespace used and they cannot see each other.
For a complete example using Javascript UDFs, see here