DEV Community

Tetsuya Wakita
Tetsuya Wakita

Posted on

clasp 3 Dropped TypeScript — Here's a Vite Plugin for Google Apps Script

Write standard TypeScript with export. Build with Vite. Push with clasp.


Why I built this

clasp 3 removed built-in TypeScript support. The recommendation is to use an external bundler — Google provides google/aside for Rollup.

I wanted Vite. The existing Vite plugins for GAS were either outdated (Rhino-era transforms like arrow function conversion) or didn't support web apps. So I built gas-vite-plugin — a minimal Vite plugin that only does what Vite can't do natively for GAS.


Install

npm install -D gas-vite-plugin
Enter fullscreen mode Exit fullscreen mode

Basic usage

// vite.config.ts
import gasPlugin from "gas-vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [gasPlugin()],
  build: {
    lib: {
      entry: "src/main.ts",
      formats: ["es"],
      fileName: () => "Code.js",
    },
  },
});
Enter fullscreen mode Exit fullscreen mode
// src/main.ts
export function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("My Menu")
    .addItem("Run", "myFunction")
    .addToUi();
}

export function myFunction() {
  SpreadsheetApp.getActiveSpreadsheet().toast("Hello from Vite!");
}
Enter fullscreen mode Exit fullscreen mode
npx vite build   # → dist/Code.js (exports stripped) + dist/appsscript.json
npx clasp push
Enter fullscreen mode Exit fullscreen mode

The plugin strips export keywords, copies appsscript.json, and sets GAS-safe defaults (no minification, no code splitting). That's the zero-config experience.


Features

Feature Description
Export stripping Removes export so functions are callable by GAS
Manifest copy Copies appsscript.json to dist automatically
File include Copies HTML/CSS flat to dist for web apps
Tree-shake protection Keeps functions that GAS calls by string name
GAS-safe defaults No minification, no code splitting, V8 assumed

Options

gasPlugin({
  manifest: "src/appsscript.json", // default
  include: ["src/**/*.html"],       // copy files flat to dist
  globals: ["processData"],         // protect from tree-shaking
  autoGlobals: true,                // auto-protect exports (default)
});
Enter fullscreen mode Exit fullscreen mode

globals — when tree-shaking removes too much

GAS calls functions by string name (menu handlers, triggers, google.script.run). If a function isn't exported and isn't referenced in code, Vite removes it.

export function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("Tools")
    .addItem("Process", "processData") // string reference — Vite can't see this
    .addToUi();
}

function processData() { /* removed by tree-shaking */ }
Enter fullscreen mode Exit fullscreen mode

Fix: add it to globals.

gasPlugin({ globals: ["processData"] })
Enter fullscreen mode Exit fullscreen mode

Exported functions are protected automatically (autoGlobals: true by default).


Web app support

GAS web apps need HTML files and backend functions callable via google.script.run. Use include for files and globals for the backend functions:

// vite.config.ts
gasPlugin({
  include: ["src/**/*.html"],
  globals: ["getData", "saveData"],
})
Enter fullscreen mode Exit fullscreen mode
// src/main.ts
export function doGet() {
  return HtmlService.createHtmlOutputFromFile("index").setTitle("My App");
}

function getData() {
  return SpreadsheetApp.getActiveSpreadsheet()
    .getActiveSheet().getDataRange().getValues();
}
Enter fullscreen mode Exit fullscreen mode
<!-- src/index.html — copied flat to dist -->
<script>
  google.script.run.withSuccessHandler(render).getData();
</script>
Enter fullscreen mode Exit fullscreen mode

Output:

dist/
├── Code.js          # exports stripped
├── appsscript.json  # auto-copied
└── index.html       # flat-copied via include
Enter fullscreen mode Exit fullscreen mode

Files are flattened — src/views/sidebar.htmldist/sidebar.html. GAS doesn't support subdirectories.


Recommended project layout

your-gas-project/
├── src/
│   ├── main.ts           # entry point
│   ├── utils.ts          # modules — import normally
│   ├── appsscript.json   # GAS manifest
│   └── index.html        # for web apps
├── dist/                  # → clasp push from here
├── vite.config.ts
├── .clasp.json            # rootDir: "dist"
└── package.json
Enter fullscreen mode Exit fullscreen mode

Links

  • GitHub — source, examples, issues
  • npmnpm install -D gas-vite-plugin

Top comments (0)