Top 2 ExtendScript Mistakes and How to Avoid Them

ExtendScript
CEP
Scripts
Adobe Creative CloudAfter EffectsPremiere ProMedia Encoder
by Justin Taylor on Dec 3, 2022
5 min read

Have you ever had trouble finding out why your script or extension doesn't work on a user's computer, but it works fine for you? Or why they're reporting your tool is causing another tool to break? The culprit more often than not is global namespace pollution or modifying prototypes.

Polluting the global namespace and modifying prototypes when building scripts and extensions for Adobe apps that use a shared environment can cause mysterious bugs in your tools and other's tools. Since the Adobe ExtendScript implementation is very different than most other development environments, this mistake is common amongst developers of all experience levels.

This guide will help you avoid these common pitfalls:

SHARED ENVIRONMENT

A shared ExtendScript environment is a development space that is common between all 3rd party scripts and extensions. Some Adobe apps utilize a shared global environment, while others employ isolated environments per extension / script. Here is the breakdown by application:

Shared Environments:

  • After Effects
  • Premiere Pro
  • Media Encoder

Isolated Environments:

  • Photoshop
  • Illustrator
  • Animate
  • Audition

What does this mean for developers? Well, if you develop tools for apps with an isolated environment like Photoshop or Illustrator, you can safely develop however you like without worrying about your code impacting other tools or vice versa.

However, if you build scripts or extensions for any app that use a shared environment like Premiere Pro, After Effects, or Media Encoder, you need to be careful not to pollute the global namespace or modify prototypes.

Global Namespace Pollution

In JavaScript, the global namespace is a container for all globally-defined variables, functions, and objects. It is the top-level scope in the JavaScript environment.

When you define a variable or function outside of any other function or block, or forget to initialize a variable, it is automatically added to the global namespace. This means that it can be accessed from anywhere in your code, and can potentially cause conflicts with other variables or functions that have the same name from someone else's tool or another code library.

// This unscoped variable pollutes the global namespace
var renderFlag = true;

(function() {
  // This uninitialized variable pollutes the global namespace
  startFlag = true;
})();

Why should we avoid global variables? Because tools using ExtendScript in Premiere and After Effects operate in a shared environment. Someone else could come along and change your global variable renderFlag and cause your tool to break. In the same way your tool can change a global variable that another tool uses and cause their tool to break. Additionally, if you include any code libraries that look for global variables to determine their environment, you could break those as well.

The easiest way to solve this is by encapsulating all of our code in an immediately-invoked function expression (IIFE) and ensure all variables are initialized, this prevents any variables, functions, or objects from leaking into the global scope.

(function() {
  // These variables do not pollute the global namespace
  // because they are scoped and initialized
  var renderFlag = true;
  var startFlag = true;
})();

This way if we try and access our variable, renderFlag, we can be sure that it can only be modified by our code and guarantee it won't cause another tool to break.

The one situation where an IIFE does not always solve our problems is for CEP Extensions. Since CEP Extensions consist of a JavaScript layer and an ExtendScript layer, we need some way to call our functions in ExtendScript from JavaScript. The solution to this is to encapsulate all of our functions into a single unique variable that we can be confident won't conflict with other tools.

The way we prefer to solve this, is by appending a single scoped variable of the panel id to the dollar object ($) in ExtendScript to act as our unique namespace:

$['com.my.extension'] = {
  renderFlag: true,
  helloWorld:  () => {
    alert("Hello World!");
  }
}

Then on the JavaScript side we can access this function like so:

const csi = new CSInterface();
csi.evalScript(`$['com.my.extension'].helloWorld();`);

Our boilerplate for CEP Extensions, Bolt CEP, makes this process even simpler by abstracting away the scoping so scoped functions can be accessed simply with:

evalES(`helloWorld()`);

See more info on that usage here: Bolt CEP: Calling ExtendScript from JS.

Modifying the Prototype

Equally as fatal as polluting the global namespace is modifying the prototype. This refers to any global prototypes provided by the Adobe API, such as Date(), File(), Array(), Application(), and so on.

Many developers will modify prototypes, also known as polyfilling, in order to add missing functionality in older versions of JavaScript. For example, ES3 ExtendScript, does not include modern array prototypes like map, filter, and forEach. A common way to polyfill these features could look like.

Array.prototype.forEach = function(callback, thisArg) {
  for (var i = 0; i < this.length; i++) {
    callback.call(thisArg, this[i], i, this);
  }
}

Seems harmless, right? Well not exactly. In a isolated environment such as your own website or Adobe apps with isolated ExtendScript environments like Photoshop or Illustrator, this is perfectly acceptable, however Adobe apps that use a shared ExtendScript environment like After Effects and Premiere, this can break your tools, other tools, and even code libraries you're using.

For example, say you polyfill the Array.map() prototype correctly, but another tool comes along and polyfills it with their own implementation that doesn't return the same results. Now your tool is broken and it's hard to discover why. The same could take place if you incorrectly polyfill a prototype causing another tool to break.

The way to avoid this issue altogether is by using helper functions instead of polyfills. This way we leave the global prototypes alone and avoid any potential conflicts. A simple example of a forEach() helper function could look like:

function forEach(array, callback) {
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i, array);
  }
}

Now, we can use our forEach() helper function whenever we'd like, and not worry about breaking someone else's tool or our tool being broken by someone else's polyfill.

Being a Good Citizen

Preventing global namespace pollution by scoping our variables and avoiding extending prototypes by using helper functions instead, ensures that the code we write today and in the future works reliably alongside other tools in Adobe 3rd party tool environment.

If you want to start off development on the right foot, check out Bolt CEP which comes pre-configured to scope all your functions to avoid any namespace pollution and examples for helper functions to incorporate modern JS functionality into ExtendScript:

And if you're new to Adobe Extensions, check out our post on Building Adobe Extensions and the differences between Scripts vs Extensions vs Plugins.

Have questions? Reach out to the Hyper Brew team and we'd be happy to help!