If you have an existing script using the Rhino runtime and want to make use of V8 syntax and features, you must migrate the script to V8.
Most scripts written using the Rhino runtime can operate using a V8 runtime without adjustment. Often the only prerequisite to adding V8 syntax and features to a script is enabling the V8 runtime.
However, there is a small set of incompatibilities and other differences that can result in a script failing or behaving unexpectedly after enabling the V8 runtime. As you migrate a script to use V8, you must search the script project for these issues and correct any you find.
V8 migration procedure
To migrate a script to V8, follow this procedure:
- Enable the V8 runtime for the script.
- Carefully review the incompatibilites listed below. Examine your script to determine if any of the incompatibilities are present; if one or more incompatibilities are present, adjust your script code to remove or avoid the issue.
- Carefully review the other differences listed below. Examine your script to determine if any of the listed differences impact your code's behavior. Adjust your script to correct the behavior.
- Once you have corrected any discovered incompatibilities or other differences, you can begin updating your code to use V8 syntax and other features as desired.
- After finishing your code adjustments, thoroughly test your script to make sure it behaves as expected.
- If your script is a web app or published add-on, you must create a new version of the script with the V8 adjustments. To make the V8 version available to users, you must re-publish the script with this version.
Incompatibilities
The original Rhino-based Apps Script runtime unfortunately permitted several non-standard ECMAScript behaviors. Since V8 is standards compliant, these behaviors aren't supported after migration. Failing to correct these issues results in errors or broken script behavior once the V8 runtime is enabled.
The following sections describe each of these behaviors and steps you must take to correct your script code during migration to V8.
Avoid for each(variable in object)
The
for each (variable in object)
statement was added to JavaScript 1.6, and removed in favor of for...of
.
When migrating your script to V8, avoid using for each (variable in object)
statements.
Instead, use for (variable in object)
:
// Rhino runtime var obj = {a: 1, b: 2, c: 3}; // Don't use 'for each' in V8 for each (var value in obj) { Logger.log("value = %s", value); } |
// V8 runtime var obj = {a: 1, b: 2, c: 3}; for (var key in obj) { // OK in V8 var value = obj[key]; Logger.log("value = %s", value); } |
Avoid Date.prototype.getYear()
In the original Rhino runtime,
Date.prototype.getYear()
returns two-digit years for years from 1900-1999, but four-digit years for other
dates, which was the behavior in JavaScript 1.2 and earlier.
In the V8 runtime,
Date.prototype.getYear()
returns the year minus 1900 instead as required by
ECMAScript standards.
When migrating your script to V8, always use
Date.prototype.getFullYear()
,
which returns a four-digit year regardless of the date.
Avoid using reserved keywords as names
ECMAScript prohibits the use of certain reserved keywords in function and variable names. The Rhino runtime allowed many of these words, so if your code uses them, you must rename your functions or variables.
When migrating your script to V8, avoid naming variable or functions
using one of the
reserved keywords.
Rename any variable or function to avoid using the keyword name. Common uses
of keywords as names are class
, import
, and export
.
Avoid reassigning const
variables
In the original Rhino runtime, you can declare a variable using const
which
means the value of the symbol never changes and future assignments to the
symbol are ignored.
In the new V8 runtime, the const
keyword is standard compliant and assigning
to a variable declared as a const
results in a
TypeError: Assignment to constant variable
runtime error.
When migrating your script to V8, do not attempt to reassign the value of
a const
variable:
// Rhino runtime const x = 1; x = 2; // No error console.log(x); // Outputs 1 |
// V8 runtime const x = 1; x = 2; // Throws TypeError console.log(x); // Never executed |
Avoid XML literals and the XML object
This non-standard extension to ECMAScript allows Apps Script projects to use XML syntax directly.
When migrating your script to V8, avoid using direct XML literals or the XML object.
Instead, use the XmlService to parse XML:
// V8 runtime var incompatibleXml1 = <container><item/></container>; // Don't use var incompatibleXml2 = new XML('<container><item/></container>'); // Don't use var xml3 = XmlService.parse('<container><item/></container>'); // OK |
Don't build custom iterator functions using __iterator__
JavaScript 1.7 added a feature to allow adding a custom iterator to any clas
s by declaring a __iterator__
function in that class's prototype; this was
also added into Apps Script's Rhino runtime as a developer convenience. However,
this feature was never part of the
ECMA-262 standard
and was removed in ECMAScript-compliant JavaScript engines. Scripts using V8
can't use this iterator construction.
When migrating your script to V8, avoid __iterator__
function to build
custom iterators. Instead,
use ECMAScript 6 iterators.
Consider the following array construction:
// Create a sample array var myArray = ['a', 'b', 'c']; // Add a property to the array myArray.foo = 'bar'; // The default behavior for an array is to return keys of all properties, // including 'foo'. Logger.log("Normal for...in loop:"); for (var item in myArray) { Logger.log(item); // Logs 0, 1, 2, foo } // To only log the array values with `for..in`, a custom iterator can be used. |
The following code examples show how an iterator could be constructed in the Rhino runtime, and how to construct a replacement iterator in the V8 runtime:
// Rhino runtime custom iterator function ArrayIterator(array) { this.array = array; this.currentIndex = 0; } ArrayIterator.prototype.next = function() { if (this.currentIndex >= this.array.length) { throw StopIteration; } return "[" + this.currentIndex + "]=" + this.array[this.currentIndex++]; }; // Direct myArray to use the custom iterator myArray.__iterator__ = function() { return new ArrayIterator(this); } Logger.log("With custom Rhino iterator:"); for (var item in myArray) { // Logs [0]=a, [1]=b, [2]=c Logger.log(item); } |
// V8 runtime (ECMAScript 6) custom iterator myArray[Symbol.iterator] = function() { var currentIndex = 0; var array = this; return { next: function() { if (currentIndex < array.length) { return { value: "[${currentIndex}]=" + array[currentIndex++], done: false}; } else { return {done: true}; } } }; } Logger.log("With V8 custom iterator:"); // Must use for...of since // for...in doesn't expect an iterable. for (var item of myArray) { // Logs [0]=a, [1]=b, [2]=c Logger.log(item); } |
Avoid conditional catch clauses
The V8 runtime doesn't support catch..if
conditional catch clauses, as they
are not standard-compliant.
When migrating your script to V8, move any catch conditionals inside the catch body:
// Rhino runtime try { doSomething(); } catch (e if e instanceof TypeError) { // Don't use // Handle exception } |
// V8 runtime try { doSomething(); } catch (e) { if (e instanceof TypeError) { // Handle exception } } |
Avoid using Object.prototype.toSource()
JavaScript 1.3 contained a Object.prototype.toSource() method that was never part of any ECMAScript standard. It is not supported in the V8 runtime.
When migrating your script to V8, remove any use of Object.prototype.toSource() from your code.
Other differences
In addition to the above incompatibilities that can cause script failures, there are a few other differences that, if uncorrected, may result in unexpected V8 runtime script behavior.
The following sections explain how to update your script code to avoid these unexpected surprises.
Adjust locale-specific date and time formatting
The Date
methods toLocaleString()
,
toLocaleDateString()
,
and toLocaleTimeString()
behave
differently in the V8 runtime as compared to Rhino.
In Rhino, the default format is the long format, and any parameters passed in are ignored.
In the V8 runtime, the default format is the short format and parameters
passed in are handled according to the ECMA standard (see the
toLocaleDateString()
documentation
for details).
When migrating your script to V8, test and adjust your code's expectations regarding the output of locale-specific date and time methods:
// Rhino runtime var event = new Date( Date.UTC(2012, 11, 21, 12)); // Outputs "December 21, 2012" in Rhino console.log(event.toLocaleDateString()); // Also outputs "December 21, 2012", // ignoring the parameters passed in. console.log(event.toLocaleDateString( 'de-DE', { year: 'numeric', month: 'long', day: 'numeric' })); |
// V8 runtime var event = new Date( Date.UTC(2012, 11, 21, 12)); // Outputs "12/21/2012" in V8 console.log(event.toLocaleDateString()); // Outputs "21. Dezember 2012" console.log(event.toLocaleDateString( 'de-DE', { year: 'numeric', month: 'long', day: 'numeric' })); |
Avoid using Error.fileName
and Error.lineNumber
In the V8 untime, the standard JavaScript
Error
object doesn't support the fileName
or lineNumber
as constructor parameters
or object properties.
When migrating your script to V8,
remove any dependence on Error.fileName
and Error.lineNumber
.
An alternative is to use the
Error.prototype.stack
.
This stack is also non-standard, but supported in both Rhino and V8. The
format of the stack trace produced by the two platforms is slightly different:
// Rhino runtime Error.prototype.stack // stack trace format at filename:92 (innerFunction) at filename:97 (outerFunction) |
// V8 runtime Error.prototype.stack // stack trace format Error: error message at innerFunction (filename:92:11) at outerFunction (filename:97:5) |
Adjust handling of stringified enum objects
In the original Rhino runtime, using the JavaScript
JSON.stringify()
method on an enum object only returns {}
.
In V8, using the same method on an enum object retuns the enum name.
When migrating your script to V8,
test and adjust your code's expectations regarding the output of
JSON.stringify()
on enum objects:
// Rhino runtime var enumName = JSON.stringify(Charts.ChartType.BUBBLE); // enumName evaluates to {} |
// V8 runtime var enumName = JSON.stringify(Charts.ChartType.BUBBLE); // enumName evaluates to "BUBBLE" |
Adjust handling of undefined parameters
In the original Rhino runtime, passing undefined
to a method as a parameter
resulted in passing the string "undefined"
to that method.
In V8, passing undefined
to methods is equivalent to passing null
.
When migrating your script to V8,
test and adjust your code's expectations regarding undefined
parameters:
// Rhino runtime SpreadsheetApp.getActiveRange() .setValue(undefined); // The active range now has the string // "undefined" as its value. |
// V8 runtime SpreadsheetApp.getActiveRange() .setValue(undefined); // The active range now has no content, as // setValue(null) removes content from // ranges. |
Adjust handling of global this
The Rhino runtime defines an implicit special context for scripts that use it.
Script code runs in this implicit context, distinct from the actual global
this
. This means that references to the "global this
" in the code actually
evaluate to the special context, which only contains the code and variables
defined in the script. The built-in Apps Script services and ECMAScript objects
are excluded from this use of this
. This situation was similar to this
JavaScript structure:
// Rhino runtime // Apps Script built-in services defined here, in the actual global context. var SpreadsheetApp = { openById: function() { ... } getActive: function() { ... } // etc. }; function() { // Implicit special context; all your code goes here. If the global this // is referenced in your code, it only contains elements from this context. // Any global variables you defined. var x = 42; // Your script functions. function myFunction() { ... } // End of your code. }(); |
In V8, the implicit special context is removed. Global variables and functions
defined in the script are placed in the global context, beside the built-in
Apps Script services and ECMAScript built-ins like Math
and Date
.
When migrating your script to V8, test and adjust your code's expectations
regarding the use of this
in a global context. In most cases the differences
are only apparent if your code examines the keys or property names of the
global this
object:
// Rhino runtime var myGlobal = 5; function myFunction() { // Only logs [myFunction, myGlobal]; console.log(Object.keys(this)); // Only logs [myFunction, myGlobal]; console.log( Object.getOwnPropertyNames(this)); } |
// V8 runtime var myGlobal = 5; function myFunction() { // Logs an array that includes the names // of Apps Script services // (CalendarApp, GmailApp, etc.) in // addition to myFunction and myGlobal. console.log(Object.keys(this)); // Logs an array that includes the same // values as above, and also includes // ECMAScript built-ins like Math, Date, // and Object. console.log( Object.getOwnPropertyNames(this)); } |
Adjust handling of instanceof
in libraries
Using instanceof
in a library on an object that is passed as a parameter in a
function from another project can give false negatives. In the V8 runtime, a
project and its libraries are run in different execution contexts and hence have
different globals and prototype chains.
Note that this is only the case if your library uses instanceof
on an object
that is not created in your project. Using it on an object that is created in
your project, whether in the same or a different script inside your project,
should work as expected.
If a project that’s running on V8 uses your script as a library, check if your
script uses instanceof
on a parameter that will be passed from another project. Adjust
the usage of instanceof
and use other feasible alternatives as per your use
case.
One alternative for a instanceof b
can be to use the constructor of a
in
cases where you don't need to search the entire prototype chain and just check
the constructor.
Usage: a.constructor.name == "b"
Consider Project A and Project B where Project A uses Project B as a library.
//Rhino runtime //Project A function caller() { var date = new Date(); // Returns true return B.callee(date); } //Project B function callee(date) { // Returns true return(date instanceof Date); } |
//V8 runtime //Project A function caller() { var date = new Date(); // Returns false return B.callee(date); } //Project B function callee(date) { // Incorrectly returns false return(date instanceof Date); // Consider using return (date.constructor.name == // “Date”) instead. // return (date.constructor.name == “Date”) -> Returns // true } |
Another alternative can be to introduce a function that checks instanceof
in the main project
and pass the function in addition to other parameters when calling a library function. The passed function
can then be used to check instanceof
inside the library.
//V8 runtime //Project A function caller() { var date = new Date(); // Returns True return B.callee(date, date => date instanceof Date); } //Project B function callee(date, checkInstanceOf) { // Returns True return checkInstanceOf(date); } |
Adjust passing of non-shared resources to libraries
Passing a non-shared resource from the main script to a library works differently in the V8 runtime.
In the Rhino runtime, passing a non-shared resource won't work. The library uses its own resource instead.
In the V8 runtime, passing a non-shared resource to the library works. The library uses the passed non-shared resource.
Do not pass non-shared resources as function parameters. Always declare non-shared resources in the same script that uses them.
Consider Project A and Project B where Project A uses Project B as a library. In this example, PropertiesService
is a non-shared resource.
// Rhino runtime // Project A function testPassingNonSharedProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-A'); B.setScriptProperties(); // Prints: Project-B Logger.log(B.getScriptProperties( PropertiesService, 'project')); } |
// V8 runtime // Project A function testPassingNonSharedProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-A'); B.setScriptProperties(); // Prints: Project-A Logger.log(B.getScriptProperties( PropertiesService, 'project')); } |
Update access to standalone scripts
For standalone scripts running on V8 runtime, you need to provide users at least view access to the script in order for the script's triggers to work properly.