Skip to main content

Google Apps Script and ES Modules

Currently, Google Apps Script does not support ES modules - and any usage of export/import will fail.

One way of handling this is to use rollup.js to bundle your project into one single JavaScript file.

The trick here is to make sure not to export any functions in your entry point code, e.g. index.ts, and to prevent any generation of export statement in the final bundle (see the custom rollup plugin in the rollup.config.js below).

import { babel } from "@rollup/plugin-babel";
import { nodeResolve } from "@rollup/plugin-node-resolve";

const extensions = [".ts", ".js"];

const preventThreeShakingPlugin = () => {
    return {
      name: 'no-threeshaking',
      resolveId(id, importer) {
        if (!importer) {
            // let's not theeshake entry points, as we're not exporting anything in Apps Script files
          return {id, moduleSideEffects: "no-treeshake" }
        return null;

export default {
  input: "./src/index.ts",
  output: {
    dir: "build",
    format: "esm",
  plugins: [
    babel({ extensions, babelHelpers: "runtime" }),
In order to use babel to transpile the code, be sure to add a babel.config.js to the project:
module.exports = {
  presets: [
      // ES features necessary for user's Node version
        targets: {
          node: "current",
In the above example, the resulting bundle created by transpiling the entry point in ./src will end up in the ./build-folder. To make sure Clasp picks the right files to push, a .claspignore-file must be added to the project:

# ignore all files…

# except the extensions…

# ignore even valid files if in…
This should be enough to start developing a project using ES Modules.


Popular posts from this blog

GWT and Spring Security

Update! - Based on the post below, and my other post regarding Spring Security and OpenID, I have added Open-ID support to the sample application below. For those interested, here's the write-up of changes. I've spent quite some time digging into ways of integrating GWT and Spring Security. It all started by reading the following post in the GWT Forum - Best practices/ideas for GWT with Spring Security (or equivalent) , and then checking out this blog - GWT and Spring Security . To make matters worse, I started reading Security for GWT Applications and specifically about the "Cross-Site Request Forging"-attacks. Now, what could I do about it? Well, starting by setting up my own project (Maven-based) with all updated dependencies (GWT 2.0.3 etc) and started reading the Spring Security Reference Documentation (puh!). Instead of See Wah Cheng's approach of implementing a custom authentication service, I decided to rely on standard namespace configuration

Loading Google Maps API asynchronously with RequireJS

With Single Page Web Applications becoming more and more popular, I decided to understand the concepts of various Javascript frameworks a little better. There are literally hundreds of them, but I decided to start with a really nice tutorial written by Alex Young. Cornerstones in this tutorial are BackboneJS, Underscore, Bootstrap and RequireJS. After been through the tutorial I decided to roll my own project based on the same setup. I wanted to use Google Maps for this, and searched for a way to load the API using RequireJS. Turned out that there are a few different approaches, but the most common seems to be to use the async-plugin created by Miller Medeiros. Jason Wyatt has another interesting solution which caught my attention. Being new to all this, I really didn't feel like start involving plug-ins from remote repositories. It might be the most natural thing to do, but one step at a time is more my melody. Jason's solution had some drawbacks mentioned in the comm

Handlebars integration with Yeoman’s webapp-generator

I was looking for an instruction on how to add Handlebars to my application based on Yeoman’s webapp-generator. There is an instruction on the old wiki on GitHub, but it’s out-of-date. For this exercise I’ve been using Yeoman 1.1.2 and the 0.47 version of the webapp-generator. First off, go ahead with step 1 and 2 (but obviously using the latest versions). There’s no need to load the "grunt-contrib-handlebars" in your Gruntfile.js (step 3), as "require('load-grunt-tasks')(grunt);" is used to load all referenced tasks. I did some changes to the file structure in step 4, but I guess that’s a matter of taste? One thing to note is the change from “temp” to “.tmp” as this is the temporary directory name being used in other places: handlebars: { compile: { files: { '.tmp/scripts/compiled-templates.js': [ '<%= %>/templates/**/*.hbs' ] }, options: { namespace: 'MyApp.Templates