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

Published: (March 13, 2026 at 02:10 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

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

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",
    },
  },
});
// src/main.ts
export function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("My Menu")
    .addItem("Run", "myFunction")
    .addToUi();
}

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

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

FeatureDescription
Export strippingRemoves export so functions are callable by GAS
Manifest copyCopies appsscript.json to dist automatically
File includeCopies HTML/CSS files flat to dist for web apps
Tree‑shake protectionKeeps functions that GAS calls by string name
GAS‑safe defaultsNo minification, no code splitting; V8 runtime 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)
});

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 */ }

Fix: add the function name to globals.

gasPlugin({ globals: ["processData"] })

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"],
});
// src/main.ts
export function doGet() {
  return HtmlService.createHtmlOutputFromFile("index").setTitle("My App");
}

function getData() {
  return SpreadsheetApp.getActiveSpreadsheet()
    .getActiveSheet().getDataRange().getValues();
}
google.script.run.withSuccessHandler(render).getData();

Output structure

dist/
├── Code.js          # exports stripped
├── appsscript.json  # auto‑copied
└── index.html       # flat‑copied via include

Files are flattened — e.g., src/views/sidebar.htmldist/sidebar.html. GAS doesn’t support sub‑directories.

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
  • GitHub — source, examples, issues
  • npmnpm install -D gas-vite-plugin
0 views
Back to Blog

Related posts

Read more »