Multiple Script Files: The problem

Apps Script V8 doesn’t have a module loader system. If you’re used to developing in NodeJs, you’ll also be familiar with importing and exporting to include required modules in your project. With Apps Script, you have no control over the order in which multiple script files are executed. In Legacy Apps Script, there seemed to be some kind of workaround going on so that global statements were executed in a sensible order (I don’t know the details), but in V8 this is not the case.

Global space

I’ve always advocated against defining anything in the Global space, and certainly nothing executable, and this is even more important with V8 where you can make no assumptions about the order. For this reason, I’ve always recommended that you use namespacing Improved namespace pattern for Apps Script both to avoid naming collisions but also to ensure correct execution order. This (longish) article will be a Sheets project to demonstrate how you can reliably use multiple scripts in V8.

Structure

This small project will include

  • class to abstract Sheets
  • class to objectify values in sheets
  • Singletons for managing access to various APIs
  • Singleton to manage app settings
  • App function to control the execution

In other words – this

I’ll be using a sheet which has some airport data, like this

App controller

We’ll fill in the details later, but here are the things the App will do. This will be the only executable function in the entire project. Everything else will be inside namespaces.

 

Settings

Like all namespaces, Settings is declared as an IEF (immediately executing function), and will contain all the parameter information required to run the App.

Note the syntax of an IEF – this imitates the behavior of  module and export as you’d find in NodeJs

 

MySheet class

This class abstracts a spreadsheet, and there will be one for each sheet the App is working with. The point here is to cache (in memory, not CacheService) the content of whatever gets read or written to the sheet.

static methods

Normally, methods in a class have access to the data of the instance of the class (this…), but occasionally you might want to expose useful methods outside the class that can be used without creating an instance of the class. These static methods have no access to the data of the instance (because there isn’t one), but can be accessed in a standalone way externally. Conversely, they cannot be accessed inside an instance with something like this.staticmethod, since they don’t actually exist in created instances. However, they can be accessed using this.constructor.method as above.

Enhancing the App controller (1)

The first couple of lines of App will instantiate mySheet, and get the data from the airport’s sheet

Here’s the first couple of rows

Shob class

It’s not very convenient working with arrays of values, so we’ll use this class to interact with the sheet data. Each MySheet is abstracted into an array of JSON objects, using this class, which is also going to be the interface to MySheet.

Enhancing the App controller (2)

The App now looks like this –

and again showing a couple of rows, now converted to JSON objects

Now we can start playing with the data – finding airports that are above 3000 metres and logging the result

Now, select all airports with height above 2000 metres and write to a seperate sheet. We’ll also add a new column, which has the height in metres rather than feet, since we prefer to work in metres.

Sunset namespace

We want to enrich the data in the sheet  with the high airports with data about sunset and sunrise, using an API that returns that given a latitude and longitude. Here’s the namespace for that

Enhancing the App controller (3)

And the final result in the new sheet is

Final app

Here’s the whole thing, with the logging removed

Libraries

Note that this doesn’t all work with libraries at the time of writing. If you want to expose a class from a library, you’ll need to make a factory function (using the old function syntax)

Similarly, v8 has a problem where namespaces defined with const or arrow functions are not visible in from libraries. Here’s a workaround

 

Summary

This example does not rely on order, all the files can be separate or combined in any order, and global space pollution is minimized. Each of the scripts is reusable so can easily be put in a library or copied by other projects. My golden rules are

  • Nothing executable should be in global space
  • Don’t rely on the order that things are processed
  • Minimize the number of executable functions (1 is good)
  • Always assume your code will be reused somewhere else.