npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-gas-react

v0.1.2

Published

Vite plugin that deploys React apps to Google Apps Script with automatic code splitting

Readme

vite-plugin-gas-react

Deploy React apps to Google Apps Script — with code splitting.

Write a standard React + Vite app. Run one command. Get a working GAS web app with lazy-loaded pages.


Why?

Google Apps Script only serves HTML files via HtmlService. External <script> tags are blocked by the CAJA sanitizer. That means:

  • No ES modules, no import(), no <script src="...">
  • You normally have to inline everything into a single HTML file
  • Code splitting? Lazy loading? Forget it.

This plugin solves all of that. You write a normal React app with React.lazy() and Vite's natural code splitting — the plugin transforms the build output into something GAS can actually serve.

How It Works

React App (Vite) → Build → GAS-compatible output → clasp push → Live web app

The plugin runs at build time and:

  1. Stores all JS server-side as .gs string variables (completely bypasses CAJA)
  2. Rewrites import() calls to fetch chunks via google.script.run.getPage()
  3. Builds a dependency graph so shared chunks load before the pages that need them
  4. Generates Code.js with doGet(), getEntryCode(), and getPage() functions
  5. Generates appsscript.json with the correct webapp configuration

At runtime, the entry JS is loaded via google.script.run.getEntryCode() and injected into a <script> tag. When you navigate to a lazy page, the chunk loader fetches its code (and any shared dependencies) the same way.

Build Output

dist/
├── index.html           ← Served by doGet() — contains the chunk loader
├── __gas_entry__.js     ← Entry bundle stored as a .gs string variable
├── __gas_chunks__.js    ← All lazy + shared chunks as .gs string variables
├── Code.js              ← Server functions: doGet(), getEntryCode(), getPage()
└── appsscript.json      ← GAS project manifest

Quick Start

1. Install

npm install vite-plugin-gas-react
npm install -D vite @vitejs/plugin-react

2. Configure Vite

// vite.config.ts
import { createGASViteConfig } from 'vite-plugin-gas-react';

export default createGASViteConfig({
  clientRoot: 'src',
  appTitle: 'My App',
});

That's it. createGASViteConfig() sets up React, the GAS plugin, aliases, and dev mode automatically.

3. Write Your App

Use React.lazy() for page-level code splitting — Vite will split them into separate chunks, and the plugin will handle the rest:

// src/App.tsx
import { useState, Suspense, lazy } from 'react';

const Home = lazy(() => import('./pages/Home'));
const Settings = lazy(() => import('./pages/Settings'));

const pages = { Home, Settings };

export default function App() {
  const [page, setPage] = useState('Home');
  const Page = pages[page];

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Page />
    </Suspense>
  );
}
// src/pages/Home.tsx
export default function Home() {
  return <h1>Home Page</h1>;
}

4. Set Up Clasp

npm install -g @google/clasp
clasp login
clasp create --type webapp --rootDir dist

5. Build & Deploy

npx vite build
cd dist && clasp push

Open the GAS web app URL and your React app is live.

Configuration

createGASViteConfig(options?)

Returns a complete Vite config. All options are optional:

| Option | Default | Description | |---|---|---| | clientRoot | 'src/client' | Path to client source directory | | outDir | 'dist' | Build output directory | | appTitle | 'GAS App' | Title shown in the browser tab | | devServerPort | 3001 | Port for local dev API server | | devPort | 5173 | Vite dev server port | | aliases | {} | Additional path aliases (@src/ is automatic) | | plugins | [] | Additional Vite plugins | | vite | {} | Override/extend any Vite config option |

gasPlugin(options?)

Use this if you're building your own Vite config instead of using createGASViteConfig():

import { gasPlugin } from 'vite-plugin-gas-react';
import react from '@vitejs/plugin-react';

export default {
  plugins: [react(), gasPlugin({ appTitle: 'My App' })],
};

| Option | Default | Description | |---|---|---| | pagePrefix | 'page_' | Prefix for page chunk names | | appTitle | 'GAS App' | Web app title |

isLocalDev()

Returns true when GAS_LOCAL=true is set in environment. Use to branch behavior between local development and GAS deployment:

if (isLocalDev()) {
  // Local dev: use mock data or local API
} else {
  // Production: use google.script.run
}

How Code Splitting Works

Vite naturally splits your app into chunks:

  • Entry chunk — React, your app shell, shared dependencies
  • Page chunks — One per React.lazy(() => import('./pages/X')) call
  • Shared lib chunks — Common dependencies used by multiple pages (e.g., MUI components)

The plugin transforms these into GAS-compatible form:

| Vite Output | Plugin Transform | GAS Runtime | |---|---|---| | assets/index-abc.js (entry) | __GAS_ENTRY_CODE__ string variable | Loaded via getEntryCode() | | assets/Home-xyz.js (page) | __GAS_CHUNK_page_Home__ string variable | Loaded via getPage('page_Home') | | assets/Stack-def.js (shared lib) | __GAS_CHUNK_lib_Stack__ string variable | Auto-loaded as dependency |

Each chunk type gets its own isolated namespace to prevent variable collisions:

  • Entry exports → window.__gasEntry__
  • Shared lib exports → window.__gasLib_<name>__
  • Page exports → window.__gasChunkExports (per-chunk, cleaned up after load)

The plugin automatically builds a dependency graph. When you navigate to Home, the loader first loads lib_Stack (if not cached), then loads page_Home. All subsequent navigations to pages sharing the same libs skip reloading them.

Local Development

Set GAS_LOCAL=true to run your app with Vite's dev server instead of deploying to GAS:

GAS_LOCAL=true npx vite

In local mode, createGASViteConfig():

  • Skips the GAS plugin entirely
  • Injects window.__GAS_DEV_MODE__ = true
  • Injects window.__GAS_DEV_SERVER__ pointing to your local API server
  • Enables Vite HMR and hot reload

You can use these globals in your app to switch between google.script.run calls (production) and fetch() calls (local dev).

Requirements

  • Node.js ≥ 18
  • Vite ≥ 5
  • React 18 or 19 (with React.lazy() for code splitting)
  • clasp CLI for deployment (npm install -g @google/clasp)

Limitations

  • GAS execution time limits still apply (6 min/execution, 30 sec for web app requests)
  • Chunk loading adds a round-trip per chunk on first navigation (chunks are cached after first load)
  • No SSR — this is a client-side React app served via HtmlService
  • File size — GAS has a 50MB total project size limit. Large apps with many dependencies should be fine, but keep an eye on it.

License

MIT