A Modern Gmail Add-on Build Process

Jef Curtis
Clio Labs
Published in
7 min readOct 3, 2018

--

Let me help you avoid the pitfalls and work in a sane development environment

Today, we launched a new Gmail add-on that gives our customers the ability to interact with Clio directly within Gmail. For many of our customers, logging email messages and saving attachments to Clio is critical for keeping track of client communications and is performed multiple times throughout the day. This add-on seeks to help make tasks like these easier by allowing them to be done in the same place where those emails are read and composed.

The add-on itself is a panel that becomes visible when you open an individual message thread in Gmail. This panel can be used to provide additional information, display UI elements to allow users to interact with external services, or to take other actions without leaving Gmail. These add-ons are available for both the web and Android (in the Gmail Android app) and the experience is very consistent between them. A single add-on project will look and work the same on both platforms.

Getting started using the example markup and the UI card system is simple enough. Everything is executed on Google’s own servers using an internal protocol which pushes the UI to the add-on. Nothing is actually run client-side, which means you can’t use CSS, custom HTML templates, or libraries like React and Angular. While this might make the user experience more consistent from add-on to add-on, it does impose constraints on developers who are accustomed to using these tools. This post will discuss how to leverage the modern javascript toolchain while working in this ecosystem.

First, we should head over to the quickstart guide and complete the steps to set up our Gmail add-on. This gives us a look at a basic workflow and it should include everything we need to deploy an add-on that we can see in action right away.

Let’s set up our environment

Here at Clio, we write most of our front-end code using TypeScript. We’ll also be using Yarn and Webpack as well to help us build our Apps Script and manifest files.

yarn add typescript --dev
yarn add webpack --dev
yarn add ts-loader --dev

Since Google exposes several of their services for generating UI and accessing messages on a global scope, we need to tell TypeScript about them.

yarn add @types/google-apps-scripts --dev# if we are planning on connecting to non-Google services
yarn add @types/google-apps-script-oauth2 --dev

In order to keep our source code and configurations separate, I suggest a specific directory layout and all the following code will assume this layout.

.
+--- /build
+--- code-to-deploy.gs
+--- /config
+--- /dev
+--- /prod
+--- /webpack
+--- webpack.config.js
+--- /src
+--- our-add-on-code.ts

Next, we’ll set up our Webpack.config.js file.

This is a fairly generic Webpack configuration which uses ts-loader to transpile our TS files and bundles them up into a single build.gs file. Notice our entry point is called main.ts and our output path compiles to /build. We'll add the main.ts file to the src directory and build a simpleUI element using the CardService library so we can test that everything is working the way we expect when we view the add-on in Gmail.

In our main.ts file, we can see our entry point handler is attached the object named global. In order for Google Apps Script to call an entry point function, such as the one above, it must be a top-level function. Having all our work live on the root scope can become a challenge as our add-on grows in complexity. We want to take advantage of ES6 modules to import and export our code and to let Webpack bundle all our files into a neat build for us. We’ll fix this by using a Webpack plugin called gas-webpack-plugin. Whenever a function assignment expression is added to the global object it will generate a top-level function statement for us!

yarn add gas-webpack-plugin --dev

We’ll update the webpack.config.js file by adding the plugin.

We also need to tell TypeScript compiler about this new global object by declaring it using a declaration file named global.d.ts and adding it to the root directory.

Speaking of TypeScript, let’s add a tsconfig.json file with some common compiler options as well as the the output and include locations.

So far, so good. We can add a “build” script to the package.json file that will allow us to compile and output our code to the build directory on every save.

yarn build

Right, the initial setup for our local environment is complete. But how do we push our code up to Google? We still need to add a manifest file and some tools to make it easier to publish our code.

Let’s write a manifest

Now that we have a working environment, we’ll need a manifest file for our project. Since these files can differ quite a bit between deploy environments (whitelisted URLs, image assets, and project names to name a few differences) we’ll generate this JSON file using the Webpack plugin html-webpack-plugin and ejs templating.

yarn add html-webpack-plugin --dev

Add the template file to the config directory.

And finally, we’ll update the Webpack config file to include the plugin which will pass configurable parameters into the template. In the future, it will be easy to make these parameters environment dependant but for now, we’ll just pass a project name.

Now, when we run yarn build, our build directory should be populated with our new add-on files.

.
+--- /build
+--- build.gs
+--- appsscript.json

Let’s push some code

Now we have all the requirements of a working Gmail add-on, but it still lives on our machine. Let’s take advantage of Google CLI tool Clasp and automate our deployment workflow. Follow the guide to log into Clasp and generate a script project where the add-on will live. We can name the project anything we like as it isn’t tied to any configuration here in our setup.

yarn @google/clasp -g
clasp create my-gmail-add-on

After creating our project using Clasp, we should see two new files on the root of the project.

.appsscript.json
.clasp.json

Since we have already handled the manifest file creation workflow in the previous section, this generated .appsscript.json file is redundant and it can be removed.

The other generated file (.clasp.json) is used to determine where Clasp will deploy our new script project when running clasp push.

Right now, Clasp will deploy to our personal AppScript projects directory that we set up above, but mostly likely we’ll want a production version of the add-on to be deployed to an entirely different location. While we are still developing our add-on, we do want to use this clasp file, but it’s only for our local development environment and should be git ignored as other developers will have their own clasp file and we don’t want to override them.

In order to differentiate between .clasp.json files, we’ll move this one into a new directory labeled dev inside the configuration directory and tell Webpack that this file is our default clasp config file. Later, when a production location is ready, we can add a new clasp config file to a directory named prod and use that file to deploy new versions to our production environment. With this setup, we can release new add-on versions by just changing our deploy_env to production.

.
+--- /config
+--- /dev
+--- .clasp.json <== our new file
+--- /build
+--- /src

Now that we have a clasp.json file and Clasp installed, we want the ability to push our code changes. Let’s configure Webpack to do exactly that for us on save.

The above changes copy the .clasp.json file from the dev directory into the build directory and runs the command yarn push after each new compile. This means that every time we save changes locally, that change will be reflected in the hosted project at script.google.com. Next we’ll need to add the push script command that will be run on save to package.json. It would be a good idea to rename the build command to sync as well, as it’s a more appropriate name for this functionality after our latest changes to Webpack.

Now, we can run the yarn sync command in terminal to start an ongoing process that will continue to monitor our code changes, compile on save, and push to google.script.com. Now we should have everything we need to get started developing the next great add-on. 🎉

Deploying to production

With this setup, deploying to production only requires utilization of the node environment and a new clasp file. This clasp file can be generated logging into Clasp with a production account and running clasp create to create a new project meant for production use. We can move the .clasp.json file into the prod directory and trash the created manifest file as we are handling that through the manifest template work from a previous step.

.
+--- /config
+--- /prod
+--- .clasp.json <== production script ID
+--- /build
+--- /src

We’ll update our Webpack config to create environment specific builds using CopyWebpackPlugin.

Add a new script command named build:prod to package.json that allows us to deploy to production with a single command.

That’s it! I hope this will help elevate your Gmail add-on game as it has ours here at Clio. Thanks for reading.

Did I miss something or is there a better way to configure Webpack or TypeScript? Leave it in the comments section below.

Jef Curtis is a software developer at Clio focused primarily on front end work. Being new to the development game means he has had a long list of prior jobs including golf course maintenance, bartending, and mostly recently, running a dog walking service. He enjoys spending time traveling with his wife and family and lacing on the skates for beer league hockey.

--

--