Introduction
This post first appeared on my blog at www.mikenikles.com.
I recently found myself in a situation where I needed a Google Docs add-on to help with authoring my upcoming book, Cloud Native Web Development.
If you're not familiar with these add-ons, they are "customized extensions of G Suite productivity applications such as Gmail, Google Sheets and Google Docs." You can learn more by reading the official documentation.
The application development platform used to develop add-ons is called Google Apps Script and developers use JavaScript or, as you will see in this blog post, Typescript to write code.
Custom sidebars
A feature that caught my attention is custom sidebars. Imagine you have a Google Docs document open - a custom sidebar would appear on the right-hand side of the screen.
Sidebars are HTML files styled with CSS and JavaScript is used to interact with the server-side code by leveraging an asynchronous client-side JavaScript API, google.script.run
. A lot more details are available in the documentation.
User interface
While reading the documentation to familiarze myself with Apps Script and custom sidebars, I found a quickstart sample that used jQuery and plain CSS. A good start for sure, but you wouldn't be reading this blog post if I had accepted that technology stack ;-).
I wondered... "If it's HTML, CSS and JavaScript, there must be a way to use Svelte! 🤔"
If you're curious why I prefer Svelte over React, please check out why I moved from React to Svelte.
The default Svelte template provides a build
NPM script that compiles Svelte components into static HTML pages. Let's see how we can take advantage of that.
A Google Docs add-on Svelte template
First things first: The template repository is available at https://github.com/mikenikles/google-docs-addon-svelte-template. You can simply clone this repository, follow the instructions in the README
and you're up and running in no time. To learn about the steps I took, have a look at the closed pull requests or keep reading.
Secondly, remember at the beginning I said I needed a Google Docs add-on? If you're curious to see it all in action in a bigger project, please check out https://github.com/mikenikles/markua-docs-addon.
Initialize all tooling (npm, clasp)
Note: I recommend you clone my template above to get started and save yourself the following steps! This is mainly for the curious who want to understand what's going on.
Files changed in pull request #1
- To start, let's create an empty folder and run
npm init
- use values of your choice to complete the wizard. -
Next, we need two dev dependencies:
npm i -D @google/clasp @types/google-apps-script
clasp
is an open-source tool, separate from the Apps Script platform, that lets you develop and manage Apps Script projects from your terminal rather than the Apps Script editor.
The Apps Script types assist with autocomplete in editors and I highly recommend you install them as it makes navigating the API much simpler. -
Also create a
src/index.ts
file, mainly a placeholder for now, with the following content:
/** * @OnlyCurrentDoc */ const onOpen = (e) => { DocumentApp.getUi().createAddonMenu() .addItem("Show sidebar", "showSidebar") .addToUi(); } const onInstall = (e) => { onOpen(e); } const showSidebar = () => { // TODO: Display the sidebar Logger.log("TODO: Display the sidebar."); }
-
Later when we deploy the add-on, we want to publish only what's absolutely necessary - which isn't much. Let's instruct
clasp
accordingly with a.claspignore
file:
**/** !appsscript.json !src/**/**
-
Next up, create an
appsscript.json
file with information about the add-on:
{ "timeZone": "America/New_York", "dependencies": {}, "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8", "oauthScopes": [ "https://www.googleapis.com/auth/documents.currentonly", "https://www.googleapis.com/auth/script.container.ui" ] }
The two
oauthScopes
you see ask for permissions when users install the add-on. They're saying "I the add-on promise to only access the current document and I would like to show you a UI (the custom sidebar)". Lastly, a
.gitignore
file withnode_modules
as its content is a good idea ;-).
Add the Svelte sidebar
Files changed in pull request #2
This is the fun part where we add a Svelte application, a few NPM scripts to build the application and a workflow to use the compiled Svelte app in the add-on custom sidebar. Let's go!
-
Create the Svelte application in the
sidebar
folder:
npx degit sveltejs/template sidebar cd sidebar npm install npm run dev
Standard Svelte commands as documented. Once you verified the app runs, cancel the
npm run dev
command. Base styles will be provided by a Google-defined CSS file (more on that later), so we want to delete the
sidebar/public/global.css
file and remove its reference in thesidebar/public/index.html
file.-
It's time to automate a few steps with npm scripts. We need to
npm i -D npm-run-all
to simplify that. Next, add the following scripts to the rootpackage.json
file:
"scripts:" { "build": "rm -fr ./sidebar/public/build && run-s build:ui build:ui:generate", "build:ui": "cd ./sidebar && npm run build", "build:ui:generate": "run-p build:ui:generate:*", "build:ui:generate:css": "echo '<style>' > ./src/stylesheet.html && cat ./sidebar/public/build/bundle.css >> ./src/stylesheet.html && echo '</style>' >> ./src/stylesheet.html", "build:ui:generate:js": "echo '<script>' > ./src/javascript.html && cat ./sidebar/public/build/bundle.js >> ./src/javascript.html && echo '</script>' >> ./src/javascript.html", }
Alright... looks interesting... What's going on there?
-
build
: Deletes old artifacts and kicks off two build processes, sequentially. -
build:ui
: Changes directory to thesidebar
, our Svelte app, and runs thebuild
command defined insidebar/package.json
. This command compiles the Svelte app into static assets insidebar/public/build
. -
build:ui:generate
: Usesnpm-run-all
to execute allbuild:ui:generate:*
commands in parallel. -
build:ui:generate:css
: Not as fancy as it looks, it basically writes asrc/stylesheet.html
that contains a<style>
tag with the Svelte-generated CSS. -
build:ui:generate:js
: The same as above for CSS, but this time for JavaScript. With that, we now have asrc/stylesheet.html
and asrc/javascript.html
file which we need to load into the custom sidebar HTML.
-
-
Since these two files are generated, we don't want them in source control. The following two lines in
.gitignore
help with that:
src/javascript.html src/stylesheet.html
To create the
src/sidebar.html
file, we can start with thesidebar/public/index.html
file. Copy its content to the newsrc/sidebar.html
file.Next, remove the stylesheet link and the script tag.
-
Before the closing
</head>
tag, insert the following code to load the previously generated stylesheet file:
<?!= include("src/stylesheet"); ?>
-
In the
<body>
tag, insert the following:
<?!= include("src/javascript"); ?>
-
So close, we're almost done! Next up we need to look at that
include
function. It's something we add tosrc/index.ts
and it's a simple one-liner:
const include = (filename: string) => HtmlService.createHtmlOutputFromFile(filename).getContent();
-
Still in
src/index.ts
, it's time to update theshowSidebar
function to actually show the sidebar:
const showSidebar = () => { const ui = HtmlService.createTemplateFromFile('src/sidebar').evaluate().setTitle("My Svelte Sidebar"); DocumentApp.getUi().showSidebar(ui); }
With all that in place, npm run build
now builds the sidebar Svelte app, copies the generated CSS and JS to HTML files we can include in the sidebar HTML used by the Google Docs add-on.
Next up: Deploy and test!
Create an Apps Script project & deploy the app
As mentioned earlier, clasp
is our tool of choice to interact with Google Apps script. Let's start with three convenience NPM scripts, added to the root package.json
file:
"scripts": {
"clasp:create": "clasp create",
"clasp:login": "clasp login --no-localhost",
"clasp:open": "clasp open",
}
They're all self-explanatory.
One note though, the
--no-localhost
flag is optional and only needed if your development environment is in the cloud. If you develop locally, you don't need that flag.
-
npm run clasp:login
guides you through the process of authenticating with your Google account. -
npm run clasp:create
creates a new Apps Script project. This also generates a.clasp.json
file in your source code with the script ID. -
npm run clasp:open
opens the project, this is useful once the add-on is deployed and you want to test it or look at logs.
Deployment
One more, absolutely last NPM script for today - I promise! Let's add the following to the root package.json
file:
"scripts": {
"deploy": "run-s build && clasp push",
}
That's it. Run npm run deploy
in your terminal, give it a moment and once completed, open the Apps Script project with npm run clasp:open
.
Testing
Once you followed the steps above and have the Apps Script project open, the following steps explain how to test the add-on:
- Click Run, then Test as add-on...
- Click Select Doc and choose a document you want to use for testing.
- Click Save
- In the Execute Saved Test section that appeared at the top, select your document and click Test.
- With the document now open, navigate to Add-ons | [your-project-name] | Show sidebar
This opens the Svelte sidebar.
Optional: Interact with the Google Doc content
This is out of scope for this blog post, but the template contains an example of how a button in the sidebar can insert text into the document. Please refer to pull request #3 for details.
Please let me know what you think and if that's helpful. You can reach me on Twitter @mikenikles.