Migrer des scripts vers l'environnement d'exécution V8

Si vous disposez d'un script utilisant l'environnement d'exécution Rhino et que vous souhaitez utiliser la syntaxe et les fonctionnalités de V8, vous devez migrer le script vers V8.

La plupart des scripts écrits à l'aide de l'environnement d'exécution Rhino peuvent fonctionner à l'aide d'un environnement d'exécution V8 sans ajustement. Souvent, la seule condition préalable à l'ajout de fonctionnalités et de la syntaxe V8 à un script consiste à activer l'environnement d'exécution V8.

Cependant, il existe un petit ensemble d'incompatibilités et d'autres différences qui peuvent entraîner l'échec d'un script ou un comportement inattendu après l'activation de l'environnement d'exécution V8. Lorsque vous migrez un script pour utiliser V8, vous devez rechercher ces problèmes dans le projet de script et corriger ceux que vous trouvez.

Procédure de migration V8

Pour migrer un script vers V8, procédez comme suit:

  1. Activez l'environnement d'exécution V8 pour le script.
  2. Examinez attentivement les incompatibilités répertoriées ci-dessous. Examinez votre script pour déterminer s'il existe des incompatibilités. Si une ou plusieurs incompatibilités sont présentes, ajustez le code de votre script pour supprimer ou éviter le problème.
  3. Examinez attentivement les autres différences listées ci-dessous. Examinez votre script pour déterminer si l'une des différences listées a un impact sur le comportement de votre code. Ajustez votre script pour corriger le comportement.
  4. Une fois que vous avez corrigé les incompatibilités ou d'autres différences détectées, vous pouvez commencer à mettre à jour votre code pour utiliser la syntaxe V8 et d'autres fonctionnalités selon vos besoins.
  5. Une fois les ajustements de code terminés, testez soigneusement votre script pour vous assurer qu'il fonctionne comme prévu.
  6. Si votre script est une application Web ou un module complémentaire publié, vous devez créer une nouvelle version du script avec les ajustements V8. Pour que la version V8 soit accessible aux utilisateurs, vous devez republier le script avec cette version.

Incompatibilités

L'environnement d'exécution Apps Script d'origine basé sur Rhino autorisait malheureusement plusieurs comportements ECMAScript non standards. V8 étant conforme aux normes, ces comportements ne sont plus pris en charge après la migration. L'échec de la correction entraîne des erreurs ou un dysfonctionnement du script une fois l'environnement d'exécution V8 activé.

Les sections suivantes décrivent chacun de ces comportements et les étapes à suivre pour corriger le code de script lors de la migration vers V8.

Éviter la requête for each(variable in object)

L'instruction for each (variable in object) a été ajoutée à JavaScript 1.6 et supprimée en faveur de for...of.

Lorsque vous migrez votre script vers V8, évitez d'utiliser des instructions for each (variable in object).

Utilisez plutôt 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);
}
      

Éviter la requête Date.prototype.getYear()

Dans l'environnement d'exécution d'origine de Rhino, Date.prototype.getYear() renvoie les années à deux chiffres pour les années 1900 à 1999, mais à quatre chiffres pour les autres dates, ce qui était le comportement dans JavaScript 1.2 et versions antérieures.

Dans l'environnement d'exécution V8, Date.prototype.getYear() renvoie l'année moins 1 900, conformément aux normes ECMAScript.

Lorsque vous migrez votre script vers V8, utilisez toujours Date.prototype.getFullYear(), qui renvoie une année à quatre chiffres quelle que soit la date.

Évitez d'utiliser des mots clés réservés comme noms

ECMAScript interdit l'utilisation de certains mots clés réservés dans les noms de fonctions et de variables. L'environnement d'exécution Rhino permettait un grand nombre de ces mots. Si votre code les utilise, vous devez donc renommer vos fonctions ou variables.

Lorsque vous migrez votre script vers la version V8, évitez de nommer des variables ou des fonctions à l'aide de l'un des mots clés réservés. Renommez les variables ou les fonctions afin d'éviter d'utiliser le nom du mot clé. class, import et export sont les plus couramment utilisés comme noms.

Éviter de réaffecter const variables

Dans l'environnement d'exécution Rhino d'origine, vous pouvez déclarer une variable à l'aide de const, ce qui signifie que la valeur du symbole ne change jamais et que les futures attributions de ce symbole sont ignorées.

Dans le nouvel environnement d'exécution V8, le mot clé const est conforme aux normes et l'attribution à une variable déclarée en tant que const entraîne une erreur d'exécution TypeError: Assignment to constant variable.

Lorsque vous migrez votre script vers V8, n'essayez pas de réattribuer la valeur d'une 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
      

Éviter les littéraux XML et l'objet XML

Cette extension non standard d'ECMAScript permet aux projets Apps Script d'utiliser directement la syntaxe XML.

Lorsque vous migrez votre script vers V8, évitez d'utiliser des littéraux XML directs ou l'objet XML.

Utilisez plutôt XmlService pour analyser le code 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
      

Ne pas créer de fonctions d'itérateur personnalisées avec __iterator__

JavaScript 1.7 a ajouté une fonctionnalité permettant d'ajouter un itérateur personnalisé à n'importe quelle classe en déclarant une fonction __iterator__ dans le prototype de cette classe. Cette fonctionnalité a également été ajoutée à l'environnement d'exécution Rhino d'Apps Script pour plus de commodité. Cependant, cette fonctionnalité n'a jamais fait partie de la norme ECMA-262 et a été supprimée dans les moteurs JavaScript compatibles avec ECMAScript. Les scripts utilisant V8 ne peuvent pas utiliser cette construction d'itérateur.

Lorsque vous migrez votre script vers V8, évitez la fonction __iterator__ pour créer des itérateurs personnalisés. Utilisez plutôt des itérateurs ECMAScript 6.

Prenons la construction de tableau suivante:

// 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.
      

Les exemples de code suivants montrent comment créer un itérateur dans l'environnement d'exécution Rhino et comment créer un itérateur de remplacement dans l'environnement d'exécution 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);
}
      

Éviter les clauses d'interception conditionnelles

L'environnement d'exécution V8 n'est pas compatible avec les clauses d'interception conditionnelles catch..if, car elles ne sont pas conformes à la norme.

Lors de la migration de votre script vers V8, déplacez toutes les conditions d'interception dans le corps de l'interception:

// 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
  }
}

Éviter d'utiliser Object.prototype.toSource()

JavaScript 1.3 contenait une méthode Object.prototype.toSource() qui n'a jamais fait partie d'une norme ECMAScript. Elle n'est pas prise en charge dans l'environnement d'exécution V8.

Lorsque vous migrez votre script vers la version V8, supprimez toute utilisation de Object.prototype.toSource() de votre code.

Autres différences

Outre les incompatibilités ci-dessus qui peuvent entraîner des échecs de script, il existe quelques différences qui, si elles ne sont pas corrigées, peuvent entraîner un comportement inattendu du script d'exécution V8.

Les sections suivantes expliquent comment mettre à jour le code de votre script pour éviter ces surprises inattendues.

Ajuster la mise en forme de la date et de l'heure pour les paramètres régionaux

Dans l'environnement d'exécution V8, les méthodes Date toLocaleString(), toLocaleDateString() et toLocaleTimeString() se comportent différemment de Rhino.

Dans Rhino, le format par défaut est le format long et tous les paramètres transmis sont ignorés.

Dans l'environnement d'exécution V8, le format par défaut est le format court. Les paramètres transmis sont gérés conformément à la norme ECMA (pour en savoir plus, consultez la documentation toLocaleDateString()).

Lorsque vous migrez votre script vers la version V8, testez et ajustez les attentes de votre code concernant la sortie des méthodes de date et d'heure spécifiques aux paramètres régionaux:

// 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' }));
      

Évitez d'utiliser Error.fileName et Error.lineNumber.

Dans la version V8, l'objet JavaScript Error standard n'accepte pas fileName ou lineNumber en tant que paramètres de constructeur ou propriétés d'objet.

Lorsque vous migrez votre script vers V8, supprimez toute dépendance à Error.fileName et Error.lineNumber.

Vous pouvez également utiliser Error.prototype.stack. Cette pile n'est pas non plus standard, mais elle est compatible avec Rhino et V8. Le format de la trace de la pile produite par les deux plates-formes est légèrement différent:

// 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)
      

Ajuster la gestion des objets enum concaténés

Dans l'environnement d'exécution d'origine de Rhino, l'utilisation de la méthode JavaScript JSON.stringify() sur un objet d'énumération renvoie uniquement {}.

Dans V8, la même méthode sur un objet d'énumération permet de rétablir le nom d'énumération.

Lorsque vous migrez votre script vers la version V8, testez et ajustez les attentes de votre code concernant la sortie de JSON.stringify() sur les objets d'énumération:

// 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"

Ajuster la gestion des paramètres non définis

Dans l'environnement d'exécution d'origine de Rhino, la transmission de undefined à une méthode en tant que paramètre entraînait la transmission de la chaîne "undefined" à cette méthode.

Dans V8, la transmission de undefined aux méthodes équivaut à transmettre null.

Lorsque vous migrez votre script vers la version V8, testez et ajustez les attentes de votre code concernant les paramètres 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.

Ajuster la gestion des this globaux

L'environnement d'exécution Rhino définit un contexte implicite spécial pour les scripts qui l'utilisent. Le code de script s'exécute dans ce contexte implicite, distinct du this global réel. Cela signifie que les références à la "this" globale dans le code sont évaluées en fonction du contexte spécial, qui ne contient que le code et les variables définis dans le script. Les services Apps Script intégrés et les objets ECMAScript sont exclus de cette utilisation de this. Cette situation était semblable à la structure JavaScript suivante:

// 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.
}();

Dans V8, le contexte spécial implicite est supprimé. Les variables et les fonctions globales définies dans le script sont placées dans le contexte global, à côté des services Apps Script et des fonctions ECMAScript intégrés, tels que Math et Date.

Lorsque vous migrez votre script vers la version V8, testez et ajustez les attentes de votre code concernant l'utilisation de this dans un contexte global. Dans la plupart des cas, les différences ne sont visibles que si votre code examine les clés ou les noms de propriété de l'objet 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));
}

Ajuster la gestion de instanceof dans les bibliothèques

L'utilisation de instanceof dans une bibliothèque sur un objet transmis en tant que paramètre dans une fonction d'un autre projet peut donner des faux négatifs. Dans l'environnement d'exécution V8, un projet et ses bibliothèques sont exécutés dans différents contextes d'exécution et ont donc des éléments globaux et des chaînes de prototype différents.

Notez que ce n'est le cas que si votre bibliothèque utilise instanceof sur un objet qui n'est pas créé dans votre projet. Son utilisation sur un objet créé dans votre projet, qu'il s'agisse du même script ou d'un script différent au sein de votre projet, devrait fonctionner comme prévu.

Si un projet exécuté sur V8 utilise votre script en tant que bibliothèque, vérifiez s'il utilise instanceof sur un paramètre qui sera transmis à partir d'un autre projet. Ajustez l'utilisation de instanceof et utilisez d'autres alternatives réalisables en fonction de votre cas d'utilisation.

Une alternative pour a instanceof b peut consister à utiliser le constructeur de a dans les cas où vous n'avez pas besoin d'effectuer une recherche dans l'ensemble de la chaîne de prototypes et de simplement vérifier le constructeur. Utilisation: a.constructor.name == "b"

Prenons l'exemple du projet A et du projet B, où le projet A utilise le projet B comme bibliothèque.

//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
}

Une autre alternative peut consister à introduire une fonction qui vérifie instanceof dans le projet principal et la transmet en plus d'autres paramètres lors de l'appel d'une fonction de bibliothèque. La fonction transmise peut ensuite être utilisée pour vérifier instanceof dans la bibliothèque.

//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);
}
      

Ajuster le transfert des ressources non partagées aux bibliothèques

La transmission d'une ressource non partagée du script principal vers une bibliothèque fonctionne différemment dans l'environnement d'exécution V8.

Dans l'environnement d'exécution Rhino, la transmission d'une ressource non partagée ne fonctionne pas. La bibliothèque utilise sa propre ressource à la place.

Dans l'environnement d'exécution V8, il est possible de transmettre une ressource non partagée à la bibliothèque. La bibliothèque utilise la ressource non partagée qui a été transmise.

Ne transmettez pas de ressources non partagées en tant que paramètres de fonction. Déclarez toujours les ressources non partagées dans le script qui les utilise.

Prenons l'exemple du projet A et du projet B, où le projet A utilise le projet B comme bibliothèque. Dans cet exemple, PropertiesService est une ressource non partagée.

// Rhino runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-B
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

//Project B function setScriptProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

// V8 runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-A
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

// Project B function setProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

Mettre à jour l'accès aux scripts autonomes

Pour les scripts autonomes exécutés dans un environnement d'exécution V8, vous devez fournir aux utilisateurs au moins un accès en lecture au script pour que les déclencheurs du script fonctionnent correctement.