iTranslated by AI
Building the Ultimate clasp Development Environment through OSS
Hello 🦔
In the Google Apps Script (GAS) community, I think it has become common to perform local development using the npm library @google/clasp.
However, when building an environment to leverage modern architectures such as front-ends or TypeScript in GAS, you might find yourself discouraged many times by GAS-specific constraints and differences in the ecosystem.
"I want to write in TypeScript."
"I want to use React or Vue."
"I want to deploy with GitHub Actions."
Once you start writing code, you might encounter issues like:
"This is a GAS API, so it results in an undefined error locally..."
"If I export locally and upload to GAS, it causes an error because GAS doesn't support export..."
"The front-end works locally, but for some reason, the Web UI is completely blank in the GAS environment..."
It is not uncommon to spend all day fighting such successive errors and eventually give up.
To solve these painful points of GAS development, the OSS libraries I've been creating have finally come together to cover the entire development cycle.
I will introduce which challenges they solve in which process, along with specific code examples!
[create] Project Generator: @ciderjs/gasbombe
Challenges to Solve
- TypeScript configuration
- Bundling tools (Vite, esbuild)
- clasp configuration
- Integration with UI frameworks...
When trying to set these up in a modern configuration optimized for the GAS environment, the number of packages to install becomes enormous, and you get exhausted just by the initial setup.
You want best practices from the start.
What the Library Does
@ciderjs/gasbombe is a generator that performs all these setups at once via an interactive CLI.
npx @ciderjs/gasbombe
Just run the command and choose the pattern you want to use (React, Vue, vanilla JavaScript, etc.) and the clasp configuration method.
A configured project directory will be generated immediately while benefiting from the modern ecosystem.
Realized DevEx
Just like a propane gas cylinder (gas bombe), you can immediately get the bundle necessary for development and start developing GAS tools right away while enjoying the benefits of the modern ecosystem.
[dev] Connecting Server and Client with Types: @ciderjs/gasnuki
Challenges to Solve
Using google.script.run allows you to call server-side functions and manipulate URLs, which are essential features for modern web app development.
However, this google.script.run API is somewhat old-fashioned JavaScript, and there are tough hurdles such as:
- Types do not work even if you introduce
@types/google-apps-script. - Naturally, there are no types for the server-side functions you implement yourself.
- No Promise support (callback format).
- It doesn't work during local development.
Furthermore, if you pass data through JSON.parse, type information is lost and everything becomes any.
If you have to manually assert these one by one, type mismatches between the server-side and front-end will easily occur.
What the Library Does
@ciderjs/gasnuki analyzes the server-side code to generate type definitions and provides a Promise-based wrapper on the client side.
- Please refer to the library's README for Vite settings and CLI usage.
Server side (GAS)
import { serialize } from "@ciderjs/gasnuki/json";
export const getUserData = (id: string) => {
// Convert to JSON while preserving object type information
return serialize({ id, name: "Alice", updatedAt: new Date() });
};
Client side (React/Vue, etc.)
import { serialize } from "@ciderjs/gasnuki/json";
import {
getPromisedServerScripts,
type PartialScriptType,
} from '@ciderjs/gasnuki/promise';
import type { ServerScripts } from '~/types/appsscript';
// Define a server mock that works in local development
const mockupFunctions: PartialScriptType<ServerScripts> = {
// Simulate the getUserData function
getUserData: async (id) => {
await new Promise(resolve => setTimeout(resolve, 500)); // Wait to simulate network latency
return serialize({ id, name: 'test user', updatedAt: new Date() });
},
};
export const server = getPromisedServerScripts<ServerScripts>({ mockupFunctions, parseJson: true });
import { server } from "@/lib/server";
const fetchData = async () => {
// Function names are autocompleted, and arguments and return values are typed
// The returned Date object is also automatically restored
const user = await server.getUserData("001");
console.log(user.name);
};
Realized DevEx
You can call backend functions in a type-safe manner while utilizing IDE autocompletion.
In addition, since it automatically switches to the mock you defined during local development, you can verify the UI operation without deploying.
[dev] Library Type Definitions: @ciderjs/dgs
Challenges to Solve
When trying to use GAS libraries in a local TypeScript environment, the lack of type definitions can make you hesitate to adopt them.
Additionally, manually writing dependency information into appsscript.json—which the web editor's library import feature handles for you—is extremely difficult.
What the Library Does
@ciderjs/dgs is a CLI tool that assists with installing major GAS libraries and introducing their type definitions.
npx @ciderjs/dgs install
When you select a GAS library through the interactive prompt, it checks for the latest version using @google/clasp, updates appsscript.json, and places a type definition file (d.ts) in your local environment.
Realized DevEx
Adding external libraries, which previously depended on GAS editor features, can now be achieved in a local environment.
Furthermore, external libraries can be used in a type-safe manner, significantly reducing the stress of library management.
[dev] Router for GAS Environment: @ciderjs/city-gas
Challenges to Solve
Web apps in GAS do not allow free routing via URL paths. Also, since the APIs differ between the browser during local development (window.history) and the production GAS environment (google.script.history), code commonization is required. As a result, existing router libraries (such as react-router) cannot be used, necessitating manual routing implementation.
What the Library Does
@ciderjs/city-gas is a query-driven file-based router that absorbs these environmental differences. It includes built-in type definitions and validation for query parameters using Zod schemas. It supports React and Vue3 by default.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createRouter } from '@ciderjs/city-gas';
import { RouterProvider } from '@ciderjs/city-gas/react';
// Import automatically generated route definitions
import { pages, specialPages, dynamicRoutes } from './generated/routes';
// Initialize router
const router = createRouter(pages, { specialPages, dynamicRoutes });
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
);
import { z } from 'zod';
import { useNavigate, useParams } from '@ciderjs/city-gas/react';
// Schema definition
export const schema = z.object({
q: z.string(),
index: z.coerce.number().optional(), // Convert URL string to number
sort: z.enum(['date', 'relevance']).optional(),
});
export default function SearchPage() {
// params is inferred as { id: string; q: string; index?: number; sort?: "date" | "relevance" }
const params = useParams('/search');
const navigate = useNavigate();
const handleClick = () => {
// 1st argument: Route name (with autocomplete)
// 2nd argument: Parameters (type-checked based on schema)
navigate('/article/[id]', { id: params.id });
// replace is also possible as an option
// navigate('/', {}, { replace: true });
};
return (
<div>
<h1>Search: {params.q}</h1>
<p>Page: {params.index ?? 0}</p>
<button onClick={handleClick}>Open article: {params.id}</button>
</div>
);
}
Realized DevEx
You can implement routing without worrying about the execution environment, bringing a type-safe router library into the GAS ecosystem.
[test] Mocking GAS-specific objects: @ciderjs/vitest-plugin-gas-mock
Challenges to Solve
Since GAS-specific objects like SpreadsheetApp do not exist in the Node.js environment, even if you want to unit test only the logic of code containing them, it results in an error immediately upon execution. Manually mocking all of these involves enormous effort and incompleteness.
What the Library Does
@ciderjs/vitest-plugin-gas-mock has pre-analyzed @types/google-apps-script. Therefore, during Vitest execution, it automatically generates and injects mocks for global objects based on highly complete GAS type definition information.
// vitest.config.ts
import { mockGas } from "@ciderjs/vitest-plugin-gas-mock";
export default defineConfig({
plugins: [mockGas()],
});
// Test code
test("Getting the spreadsheet name", () => {
// Mocks are automatically connected even for deep method chains
const name = SpreadsheetApp.getActive().getName();
expect(name).toBeDefined();
});
Realized DevEx
It makes it possible to inspect only the logic even if it includes GAS APIs, helping to guarantee code quality. Additionally, since deep method chains are automatically resolved, there is no need to define mocks in detail on the test code side.
[build] Countermeasures for GAS-specific errors: vite-plugin-google-apps-script
Challenges to Solve
The GAS runtime exhibits special behavior in interpreting newlines within template literals and URL strings, which can cause the built code to crash. Also, if GAS scriptlets (<?!= ... ?>) are enclosed in double quotes, the internal JSON data gets corrupted, which also causes crashes.
What the Library Does
This plugin forces the use of Terser, which is available for compression within Vite, to replace newlines in template literals with the newline code \n. It also automatically escapes and cleans up strings and symbols that cause errors during deployment at build time.
Realized DevEx
You can focus on front-end development that doesn't crash in the GAS environment, using fast build tools like Vite with peace of mind and without being conscious of GAS-specific runtime limitations.
[build] Remove unnecessary export clauses from the backend: rolldown-plugin-remove-export
Challenges to Solve
The GAS server-side is designed to share the global scope between files, but without using export, you cannot use tools like vitest for testing. However, if you include export clauses for that purpose, the export statements will remain in the build file even after passing through a bundling tool, which causes syntax errors at runtime.
What the Library Does
This plugin removes export statements that are unnecessary for GAS from the code after bundling with rolldown (or rollup), and formats it into a shape that works as-is in GAS.
Realized DevEx
While developing with a module system, you can automatically generate code tailored to the specific execution environment of GAS.
[push] Realizing CI/CD even in GAS: @ciderjs/clasp-auth
Challenges to Solve
In automated deployments using GitHub Actions or similar tools, clasp login assumes interactive authentication through a browser, which means it cannot be executed directly in a headless CI environment. As a result, uploading to a GAS environment with clasp required manually logging in and pushing from a local machine.
What the Library Does
@ciderjs/clasp-auth provides a mechanism to encrypt local authentication credentials, upload them to GitHub Secrets, and restore them during CI execution.
# Run locally to register with Secrets
npx @ciderjs/clasp-auth upload owner/repo
name: Push to GAS
on:
push:
branches: [ "main" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup clasp auth
uses: ciderjs/clasp-auth@v0.2.0
with:
json: ${{ secrets.CLASPRC_JSON }}
- name: Install clasp
run: npm install -g @google/clasp
- name: Push to GAS
run: clasp push
Realized DevEx
You are freed from the hassle of managing authentication credentials and can safely and easily build an automated deployment environment for GAS. This makes it possible to implement workflows such as "automatically deploy to GAS when merging into the main branch" even during team development.
By combining this series of tools, we have established an environment where you can focus on the core logic development for GAS without sacrificing a modern development experience. This is achieved by systematically resolving the inconveniences that were once considered "standard" in GAS development—from environment setup to development, testing, and deployment.
In other words, you don't have to be a "GAS developer" with niche knowledge; as an "engineer," you can bring ordinary best practices from web development—such as CI/CD via GitHub Actions and type-safe development with TypeScript—directly into the GAS world.
I hope these tools serve as a catalyst to make your development even a little bit lighter.
Discussion
記事には記載してなかったですが、以下は十分な機能を持つものがあるため、開発してないです〜
vite-plugin-singlefile@google/claspの持つrunコマンド (実行可能APIとしてのデプロイが必要ですが)