Motivation

It’s good practice to keep class and namespace definitions in separate files and avoid defining functions or variables in the global space. However, App Script doesn’t give you control over the order in which it loads files. If you reference a class or a namespace from one script file, it may not yet be defined. This is where an Exports object comes in.

Using ‘var’ rather ‘const’ can help, as JavaScript var ‘hoists‘ both var and function declarations, but this won’t always solve the problem, especially with class definitions.

Many of my projects have 20 or more files and I kept running into this problem until I figured out this simple workaround. You also create a much cleaner, better documented and more flexible project.

Here’s how to use an Exports object which documents the contents of your script

Exports

You can solve this visibility problem by using an Exports obects, where you define property getters for each of your classes and namespaces.

Here’s an example of an Exports variable for a complex project with many files.

var Exports = {

/**
* Unit Class
* @implements {Unit}
*/
get Unit() {
return Unit
},

/**
* Store class
* @implements {Store}
*/
get Store() {
return Store
},

/**
* Cacher class
* @implements {Cacher}
*/
get Cacher() {
return Cacher
},

/**
* fetch class
* @implements {Fetch}
*/
get Fetch() {
return Fetch
},

/**
* AppStore class
* @implements {AppStore}
*/
get AppStore() {
return AppStore
},

/**
* DocumentAI class
* @implements {DocumentAI}
*/
get DocumentAI() {
return DocumentAI
},

/**
* Compress namespace
* @implements {Compress}
*/
get Compress() {
return Compress
},

/**
* StorePack namespace
* @implements {StorePack}
*/
get StorePack() {
return StorePack
},

/**
* Fiddler function
* @implements {Fiddler}
*/
get Fiddler() {
return Fiddler
},

/**
* deepEquals function
* @implements {DeepEqual.deepEqual}
*/
get deepEquals() {
return DeepEqual.deepEqual
},

/**
* Utils namespace
* @implements {Utils}
*/
get Utils() {
return Utils
},

/**
* UnknownPropertyError class
* @implements {UnknownPropertyError}
*/
get UnknownPropertyError() {
return UnknownPropertyError
},

/**
* UnexpectedTypeError class
* @implements {UnexpectedTypeError}
*/
get UnexpectedTypeError() {
return UnexpectedTypeError
},

/**
* UnexpectedValueError class
* @implements {UnexpectedValueError}
*/
get UnexpectedValueError() {
return UnexpectedValueError
}

}
Exports example

Why does using exports work ?

You can ensure the visibility of the Exports object by using var to hoist the object to ensure it’s visible to the other files.

Using property getters postpones the attempt to access the definition.

// this may not work, depending on order Apps Script loads its files
const ns = (()=> {
const store = new Store()
// ....
}) ()
// using an Exports object normalizes the order of accessing script definitions
const ns = (()=> {
const store = new Exports.Store()
// .....
})
Export access

JSDOC

What about JsDoc ?

You’ve probably documented each class and namespace diligently, and you want autocomplete to still work, even when wrapped in an Exports object like this.

The @implements {} directive takes care of this for you, and autocomplete information will be inherited from the referenced class.

  /**
* Store class
* @implements {Store}
*/
get Store() {
return Store
},
implements directive

Complex and library definitions

You can extend this further to implement library references, or other more complex definitions in Exports object.

This means you won’t need to change your main scripts if you, for example, flip from using inline code for testing to a library in production. You can even test a renamed variant of an existing function, or use this technique to avoid naming clashes

  /**
* deepEquals function
* @implements {DeepEqual.deepEqual}
*/
get deepEquals() {
return DeepEqual.deepEqual
},
/**
* Fiddler function
* @implements {bmFiddler.Fiddler}
*/
get Fiddler() {
return bmFiddler.Fiddler
},

// can be accessed like this, even though it's coming from a library
const fiddler = new Exports.Fiddler()

// extracts a function from a namespace
const deepEquals = Exports.deepEquals
Complex or library definitions

Find me here

Related

State management across CardService, HtmlService and Server side Add-ons

workload identity

Workload identity with Kubernetes cronjobs to synch Mongo to Bigquery

Kubernetes workload identity looks pretty scary when you read about it in the docs, but it really is a better ...
Superfetch plugin

Caching, property stores and pre-caching

I've written many times about various Apps Script caching techniques such as how to deal with size limits and use ...
document AI add-on

State management across CardService, HtmlService and Server side Add-ons

Motivation I've been working on a CardService Add-on lately which also uses HtmlService, and also runs quite a few things ...
Secret Manager

SuperFetch Plugin: Cloud Manager Secrets and Apps Script

Smg is a SuperFetch plugin to access the Google Cloud Secrets API. SuperFetch is a proxy for UrlFetchApp with additional ...
SuperFetch

Apps script caching with compression and enhanced size limitations

Motivation Caching is a great way to improve performance, avoid rate limit problems and even save money if you are ...

12 Years and 1000 pages in Office,Google (Docs,Gsuite) Workspace, and other stuff

1000 pages and counting Most years I do a post on 'a year in Apps Script', looking back over the ...
import add-on

Import, export and mix container bound and standalone Apps Script projects

This article covers how to pull scripts from multiple projects and import them into another project. You can even use ...

Apps Script server side to client side htmlservice progress reporting using CacheService

I've published a few examples of this in other projects (for example Implementing a client side progress bar reporting on ...
crusher files on drive

Sharing data between Apps Script and Node using Google Drive back end – 5 minute example

Another quick demo of data sharing Here's a challenge that shares the data in a spreadsheet with node, set up ...