clasp-types

A d.ts generator for clasp projects

Usage no npm install needed!

<script type="module">
  import claspTypes from 'https://cdn.skypack.dev/clasp-types';
</script>

README

clasp-types

npm

A TypeScript definitions generator for clasp projects to get autocomplete and type checking for your Google Apps Script Object Oriented Libraries and Client-side API.

Library: library-autocomplete

Client-side API: client-side-api-autocomplete

It works like the API Extractor, reading the @public comment annotations on any global function, class, interface or enum you want to expose, and generating d.ts files consistently.

Features

  • d.ts rollup: Generate a single d.ts from all your .ts files, wrapping global functions into Library interface.

  • Clean library API: Expose only functions and methods through @public annotation, building a cleanner interface and avoiding usage of elements not intended to be exposed.

  • Publish ready: Generate a npm package, with clear setup instructions, ready to be published.

  • Client-side API For Add-ons and Web Apps, generate types for your global functions exposed with @public, in a single d.ts file on @types folder, to get autocomplete for the server API on client.

Here is an example of library types built and published with clasp-types.

Note: clasp-types is intended for generating d.ts from Apps Script code already written in TypeScript. For generating built-in and advanced Apps Script services see https://github.com/grant/google-apps-script-dts

Install

npm i -S clasp-types

or

yarn add --dev clasp-types

Command

clasp-types

Optional params:

--src          <folder>    # default: ./src     - Source folder 
--out          <folder>    # default: ./dist    - Output folder              
--client                   # default: false     - Generate client side API types  
--root         <folder>    # default: ./        - Project root folder  

Library setup

1) Add your library namespace and name to the .clasp.json:

{
  "scriptId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF",
  "rootDir": "./src",
  "library": {
    "namespace": "gsuitedevs",
    "name": "OAuth2"
  }
}

2) Add @public comment annotation to the code you want to expose

/**
 * Create a service
 * 
 * @public
 */
function createService(serviceName: string) {
  return new Service(serviceName);
}

/**
 * The OAuth service
 * 
 * @public
 */
class Service {
  name: string;
  params_: any;
  constructor(name: string) {
    this.name = name;;
  }

  public getName() {
    return this.name;
  }
  

  /**
   * Sets an additional parameter to use when constructing the authorization URL.
   */
  public setParam(name: string, value: string): Service {
    this.params_[name] = value;
    return this;
  };

}

3) Run clasp-types to generate a npm package with index.d.ts like:

declare namespace gsuitedevs {

    /**
     * The main entry point to interact with OAuth2
     *
     * Script ID: **1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF**
     */
    export interface OAuth2 {

        /**
         * Create a service
         */
        createService(serviceName: string): Service;

    }

    /**
     * The OAuth service
     */
    export interface Service {

        getName(): string;

        /**
         * Sets an additional parameter to use when constructing the authorization URL.
         */
        setParam(name: string, value: string): Service;

    }

}

declare var OAuth2: gsuitedevs.OAuth2;

Notes:

  • On classes annotaded with @public, methods inside then should also be marked as public in order to be exposed. Private or protected methods will not be exposed.
  • Interfaces and Enumerations with @public annotation will have all members exposed by default.

A npm ready to publish package is generated in the output folder, with some setup instructions on README.md, so you can easily share your library types. Here is an example.

Suggestion: You may add a dist-tag to your types package distribution with the same version of the script, say, v23, so users can link the types version with script version, and use ones that matches.

Dependencies

If your package expose a transitive dependency on params or return types, such as GoogleAppsScript.HTML.HtmlOutput from @types/google-apps-script, add it to "dependencies" section of your package.json, instead of "devDependencies":

  "dependencies": {
    "@types/google-apps-script": "^0.0.59"
  }

So, clasp-types will correctly setup the reference on index.d.ts:

/// <reference types="google-apps-script" />

And on the resulted package.json:

  "dependencies": {
    "@types/google-apps-script": "*"
  },

Client-side API setup

1) Add @public comment annotation to the code you want to expose to client

/**
 * Execute a sum on server side, from client side
 * 
 * @public
 */
function sumOnServer(a: number, b: number): number {
  return a + b;
}

2) Run clasp-types --client to generate a index.d.ts like:

declare namespace google {

    namespace script {

        export interface Runner {

            withSuccessHandler(handler: Function): Runner;

            withFailureHandler(handler: (error: Error) => void): Runner;

            withUserObject(object: any): Runner;

            sumOnServer(a: number, b: number): void //number;
            ...

        }

        export var run: Runner;

    }
    ...

}

TypeScript on Client-side

To develop with TypeScript on client side, you should work with separated ts files and inline the corresponding js, as well as all css in the same page, in order to the resulting html template be processed by the HTML Service.

To perform the inlining a great tool is the inline-source-cli, so you can add a inline tag to your js and css references:

<head>
  ...
  <link inline href="page-style.css" rel="stylesheet">
</head>
<body>
  ...
  <script inline src="page-activity.js"></script>
  <script inline src="page-view.js"></script>
</body>

And then use a tool such as glob-exec to inline all your sources in one single script line:

glob-exec --foreach './build/**/*.html' --  'cat {{file}} | inline-source --root build > dist/{{file.name}}{{file.ext}}'

Background

The clasp-types was originally created as a foundation for the BkperApp library and the Bkper Add-on for Google Sheets, with inspirations on the API Extractor, DefinitelyTyped and previous work from grant, motemen and mtgto - thank you guys :-)

Libraries are a great way to share code between scripts, but, once published and others start using it, it requires some level of care like any other public API, so, applying some API Extractor concepts and principles help to keep the quality of the Library and avoid accidental breaks.

DefinitelyTyped is an amazing initiative and works really well for publishing types for thirdy-party libraries written in js, as well as for the Google Apps Script built-in and advanced services, although, as its recommended in the official declaration publishing documentation, for libraries written in TypeScript, build its own npm package is favored, and give some advantages:

  • Instant publishing
  • Release automation
  • Dist-tag for mapping script versions

The down side is that it requires one aditional aditional types configuration step, so, clasp-types automatically generate a package ready to publish, with instructions on README for scoped packages to setup the typeRoots and non scoped packages to setup the types.

Help welcome

  • Identify edge cases for params and return types

  • Generate d.ts fom a well documented js library, so it can also work for libraries such as OAuth2

  • Generate client ts (like this) and d.ts from openapi and API Discovery specs, for Advanced Services like libraries