El entorno de ejecución de Rhino dejará de estar disponible a partir del 31 de enero de 2026. Si tienes una secuencia de comandos existente que usa el entorno de ejecución de Rhino, debes migrarla a V8.
A menudo, el único requisito previo para agregar sintaxis y funciones de V8 a una secuencia de comandos es habilitar el entorno de ejecución de V8. Sin embargo, hay un pequeño conjunto de incompatibilidades y otras diferencias que pueden provocar que una secuencia de comandos falle o se comporte de forma inesperada en el tiempo de ejecución de V8. A medida que migras una secuencia de comandos para usar V8, debes buscar estos problemas en el proyecto de la secuencia de comandos y corregir los que encuentres.
Procedimiento de migración a la versión 8
Para migrar una secuencia de comandos a V8, sigue este procedimiento:
- Habilita el tiempo de ejecución de V8 para la secuencia de comandos. El
runtimeVersion
se puede verificar con el manifiesto del proyecto de Apps Script. - Revisa cuidadosamente las siguientes incompatibilidades. Examina tu secuencia de comandos para determinar si hay alguna incompatibilidad. Si hay una o más incompatibilidades, ajusta el código de la secuencia de comandos para quitar o evitar el problema.
- Revisa con atención las siguientes otras diferencias. Examina tu secuencia de comandos para determinar si alguna de las diferencias enumeradas afecta el comportamiento de tu código. Ajusta la secuencia de comandos para corregir el comportamiento.
- Una vez que hayas corregido las incompatibilidades o las diferencias que hayas encontrado, puedes comenzar a actualizar tu código para usar la sintaxis de V8 y otras funciones.
- Después de terminar de ajustar el código, prueba el script a fondo para asegurarte de que se comporte según lo esperado.
- Si tu secuencia de comandos es una app web o un complemento publicado, debes crear una versión nueva de la secuencia de comandos con los ajustes de V8 y dirigir la implementación a la versión recién creada. Para que la versión de V8 esté disponible para los usuarios, debes volver a publicar la secuencia de comandos con esta versión.
- Si tu secuencia de comandos se usa como biblioteca, crea una nueva implementación con versiones de tu secuencia de comandos. Comunica esta nueva versión a todos los usuarios y las secuencias de comandos que consumen tu biblioteca, y pídeles que actualicen a la versión habilitada para V8. Verifica que las versiones anteriores de tu biblioteca basadas en Rhino ya no estén en uso activo ni sean accesibles.
- Verifica que ninguna instancia de tu secuencia de comandos siga operando en el entorno de ejecución heredado de Rhino. Verifica que todas las implementaciones estén asociadas a una versión de V8. Archiva las implementaciones antiguas. Revisa todas las versiones y borra las que no usen el entorno de ejecución de V8.
Incompatibilidades
Lamentablemente, el entorno de ejecución original de Apps Script basado en Rhino permitía varios comportamientos no estándar de ECMAScript. Dado que V8 cumple con los estándares, estos comportamientos no se admiten después de la migración. Si no se corrigen estos problemas, se producirán errores o un comportamiento incorrecto de la secuencia de comandos una vez que se habilite el entorno de ejecución de V8.
En las siguientes secciones, se describen cada uno de estos comportamientos y los pasos que debes seguir para corregir el código de tu secuencia de comandos durante la migración a V8.
Evita for each(variable in object)
La instrucción for each (variable in object)
se agregó a JavaScript 1.6 y se quitó en favor de for...of
.
Cuando migres tu secuencia de comandos a V8, evita usar instrucciones for each (variable in object)
.
En su lugar, usa 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); } |
Evita Date.prototype.getYear()
En el tiempo de ejecución original de Rhino, Date.prototype.getYear()
devuelve años de dos dígitos para los años de 1900 a 1999, pero años de cuatro dígitos para otras fechas, que era el comportamiento en JavaScript 1.2 y versiones anteriores.
En el tiempo de ejecución de V8, Date.prototype.getYear()
devuelve el año menos 1900, según lo exigen los estándares de ECMAScript.
Cuando migres tu secuencia de comandos a V8, siempre usa Date.prototype.getFullYear()
, que devuelve un año de cuatro dígitos independientemente de la fecha.
Evita usar palabras clave reservadas como nombres
ECMAScript prohíbe el uso de ciertas palabras clave reservadas en los nombres de funciones y variables. El entorno de ejecución de Rhino permitía muchas de estas palabras, por lo que, si tu código las usa, debes cambiar el nombre de tus funciones o variables.
Cuando migres tu secuencia de comandos a V8, evita nombrar variables o funciones con una de las palabras clave reservadas.
Cambia el nombre de cualquier variable o función para evitar usar el nombre de la palabra clave. Algunos usos comunes de palabras clave como nombres son class
, import
y export
.
Evita reasignar variables de const
En el tiempo de ejecución original de Rhino, puedes declarar una variable con const
, lo que significa que el valor del símbolo nunca cambia y se ignoran las asignaciones futuras al símbolo.
En el nuevo entorno de ejecución de V8, la palabra clave const
cumple con los estándares, y la asignación a una variable declarada como const
genera un error de tiempo de ejecución TypeError: Assignment to constant variable
.
Cuando migres tu secuencia de comandos a V8, no intentes reasignar el valor de una variable const
:
// 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 |
Evita los literales XML y el objeto XML
Esta extensión no estándar de ECMAScript permite que los proyectos de Apps Script usen la sintaxis XML directamente.
Cuando migres tu secuencia de comandos a V8, evita usar literales XML directos o el objeto XML.
En su lugar, usa XmlService para analizar 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 |
No compiles funciones de iterador personalizadas con __iterator__
JavaScript 1.7 agregó una función para permitir agregar un iterador personalizado a cualquier clase declarando una función __iterator__
en el prototipo de esa clase. Esto también se agregó al tiempo de ejecución de Rhino de Apps Script para la comodidad de los desarrolladores. Sin embargo, esta función nunca formó parte del estándar ECMA-262 y se quitó de los motores de JavaScript que cumplen con ECMAScript. Las secuencias de comandos que usan V8 no pueden usar esta construcción de iterador.
Cuando migres tu secuencia de comandos a V8, evita la función __iterator__
para compilar iteradores personalizados. En su lugar, usa iteradores de ECMAScript 6.
Considera la siguiente construcción de arrays:
// 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. |
En los siguientes ejemplos de código, se muestra cómo se podría construir un iterador en el entorno de ejecución de Rhino y cómo construir un iterador de reemplazo en el entorno de ejecución de V8:
// 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); } |
Evita las cláusulas catch condicionales
El entorno de ejecución de V8 no admite cláusulas catch condicionales catch..if
, ya que no cumplen con los estándares.
Cuando migres tu secuencia de comandos a V8, mueve todas las condiciones catch dentro del cuerpo catch:
// 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 } } |
Evita usar Object.prototype.toSource()
JavaScript 1.3 contenía un método Object.prototype.toSource() que nunca formó parte de ningún estándar de ECMAScript. No se admite en el tiempo de ejecución de V8.
Cuando migres tu secuencia de comandos a V8, quita cualquier uso de Object.prototype.toSource() de tu código.
Otras diferencias
Además de las incompatibilidades anteriores que pueden causar fallas en las secuencias de comandos, existen otras diferencias que, si no se corrigen, podrían generar un comportamiento inesperado de la secuencia de comandos del entorno de ejecución de V8.
En las siguientes secciones, se explica cómo actualizar el código de tu secuencia de comandos para evitar estas sorpresas inesperadas.
Ajusta el formato de fecha y hora específico de la configuración regional
Los métodos Date
, toLocaleString()
, toLocaleDateString()
y toLocaleTimeString()
se comportan de manera diferente en el entorno de ejecución de V8 en comparación con Rhino.
En Rhino, el formato predeterminado es el formato largo, y cualquier parámetro que se pase se ignora.
En el tiempo de ejecución de V8, el formato predeterminado es el formato corto, y los parámetros que se pasan se controlan según el estándar de ECMA (consulta la documentación de toLocaleDateString()
para obtener más detalles).
Cuando migres tu secuencia de comandos a V8, prueba y ajusta las expectativas de tu código con respecto al resultado de los métodos de fecha y hora específicos de la configuración regional:
// 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' })); |
Evita usar Error.fileName
y Error.lineNumber
En el tiempo de ejecución de V8, el objeto Error
estándar de JavaScript no admite fileName
ni lineNumber
como parámetros del constructor ni como propiedades del objeto.
Cuando migres tu secuencia de comandos a V8, quita cualquier dependencia de Error.fileName
y Error.lineNumber
.
Una alternativa es usar Error.prototype.stack
.
Esta pila también es no estándar, pero se admite en V8. El formato del registro de seguimiento de pila que producen las dos plataformas es ligeramente diferente:
// 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) |
Se ajustó el manejo de objetos de enumeración serializados
En el tiempo de ejecución original de Rhino, usar el método JSON.stringify()
de JavaScript en un objeto de enumeración solo devuelve {}
.
En V8, usar el mismo método en un objeto de enumeración devuelve el nombre de la enumeración.
Cuando migres tu secuencia de comandos a V8, prueba y ajusta las expectativas de tu código con respecto al resultado de JSON.stringify()
en objetos de enumeración:
// 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" |
Se ajustó el control de los parámetros no definidos
En el tiempo de ejecución original de Rhino, pasar undefined
a un método como parámetro hacía que se pasara la cadena "undefined"
a ese método.
En V8, pasar undefined
a los métodos equivale a pasar null
.
Cuando migres tu secuencia de comandos a V8, prueba y ajusta las expectativas de tu código con respecto a los parámetros de undefined
:
// 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. |
Se ajustó el control de this
global
El entorno de ejecución de Rhino define un contexto especial implícito para las secuencias de comandos que lo usan.
El código de la secuencia de comandos se ejecuta en este contexto implícito, que es distinto del this
global real. Esto significa que las referencias al "this
global" en el código en realidad se evalúan en el contexto especial, que solo contiene el código y las variables definidos en el script. Los servicios integrados de Apps Script y los objetos ECMAScript se excluyen de este uso de this
. Esta situación era similar a la siguiente estructura de JavaScript:
// 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. }(); |
En V8, se quita el contexto especial implícito. Las variables y funciones globales definidas en la secuencia de comandos se colocan en el contexto global, junto con los servicios integrados de Apps Script y los elementos integrados de ECMAScript, como Math
y Date
.
Cuando migres tu secuencia de comandos a V8, prueba y ajusta las expectativas de tu código con respecto al uso de this
en un contexto global. En la mayoría de los casos, las diferencias solo son evidentes si tu código examina las claves o los nombres de las propiedades del objeto this
global:
// 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)); } |
Se ajustó el control de instanceof
en las bibliotecas
Usar instanceof
en una biblioteca en un objeto que se pasa como parámetro en una función de otro proyecto puede generar falsos negativos. En el entorno de ejecución de V8, un proyecto y sus bibliotecas se ejecutan en diferentes contextos de ejecución y, por lo tanto, tienen diferentes cadenas de prototipos y variables globales.
Ten en cuenta que esto solo sucede si tu biblioteca usa instanceof
en un objeto que no se creó en tu proyecto. Usarlo en un objeto que se crea en tu proyecto, ya sea en la misma secuencia de comandos o en una diferente dentro de tu proyecto, debería funcionar según lo esperado.
Si un proyecto que se ejecuta en V8 usa tu secuencia de comandos como biblioteca, verifica si tu secuencia de comandos usa instanceof
en un parámetro que se pasará desde otro proyecto. Ajusta el uso de instanceof
y usa otras alternativas factibles según tu caso de uso.
Una alternativa para a instanceof b
puede ser usar el constructor de a
en los casos en los que no necesites buscar en toda la cadena de prototipos y solo verificar el constructor.
Uso: a.constructor.name == "b"
Considera el proyecto A y el proyecto B, en el que el proyecto A usa el proyecto B como biblioteca.
//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 } |
Otra alternativa puede ser introducir una función que verifique instanceof
en el proyecto principal y pasar la función además de otros parámetros cuando se llama a una función de biblioteca. Luego, la función pasada se puede usar para verificar instanceof
dentro de la biblioteca.
//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); } |
Ajusta el paso de recursos no compartidos a las bibliotecas
Pasar un recurso no compartido del script principal a una biblioteca funciona de manera diferente en el entorno de ejecución de V8.
En el tiempo de ejecución de Rhino, no funcionará pasar un recurso no compartido. En su lugar, la biblioteca usa su propio recurso.
En el entorno de ejecución de V8, pasar un recurso no compartido a la biblioteca funciona. La biblioteca usa el recurso no compartido que se pasó.
No pases recursos no compartidos como parámetros de funciones. Siempre declara los recursos no compartidos en el mismo script que los usa.
Considera el proyecto A y el proyecto B, en el que el proyecto A usa el proyecto B como biblioteca. En este ejemplo, PropertiesService
es un recurso no compartido.
// 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')); } |
Actualiza el acceso a las secuencias de comandos independientes
En el caso de las secuencias de comandos independientes que se ejecutan en el entorno de ejecución de V8, debes proporcionar a los usuarios al menos acceso de visualización a la secuencia de comandos para que sus activadores funcionen correctamente.