Package Exports
- webpack-plugin-gas-react
Readme
webpack-plugin-gas-react
Webpack plugin that deploys React apps to Google Apps Script with automatic code splitting.
What's New in v0.2.0 🎉
Separate Chunk Files - Chunks are now emitted as individual files in a chunks/ directory instead of being bundled into a single __gas_chunks__.js file.
✅ Benefits:
- Better organization - each lazy-loaded component gets its own file
- Cleaner Apps Script project structure
- Easier debugging - inspect individual chunk files
- Consistent with vite-plugin approach
Before (v0.1.x):
dist/
├── Code.js
├── __gas_entry__.js
├── __gas_chunks__.js (1MB - all chunks bundled)
└── index.htmlAfter (v0.2.0):
dist/
├── Code.js
├── __gas_entry__.js
├── index.html
└── chunks/
├── __gas_chunk_164__.js
├── __gas_chunk_276__.js
├── __gas_chunk_489__.js
└── ... (one file per lazy-loaded component)Chunks still load on-demand via google.script.run.getPage() - only the file organization has changed!
Install
npm install webpack-plugin-gas-react --save-devPeer dependencies (install if missing):
npm install webpack html-webpack-plugin --save-devQuick Start
Option 1: createGASWebpackConfig (recommended)
Generates a complete webpack config — just like createGASViteConfig does for Vite:
// webpack.config.mjs
import { createGASWebpackConfig } from 'webpack-plugin-gas-react';
export default await createGASWebpackConfig({
clientRoot: 'src/client',
appTitle: 'My App',
serverEntry: 'src/server/index.ts',
});Option 2: Use GASWebpackPlugin directly
Add the plugin to your existing webpack config:
// webpack.config.mjs
import { GASWebpackPlugin } from 'webpack-plugin-gas-react';
import HtmlWebpackPlugin from 'html-webpack-plugin';
export default {
entry: './src/client/index.tsx',
output: { path: './dist', clean: true },
plugins: [
new HtmlWebpackPlugin({ template: './src/client/index.html' }),
new GASWebpackPlugin({
appTitle: 'My App',
serverEntry: 'src/server/index.ts',
}),
],
// ... rest of config
};Build & Deploy
npx webpack
cd dist && clasp pushHow It Works
React App (Webpack)
├── Client code → webpack build → code-split chunks stored as GAS string variables
└── Server code → esbuild → global functions in Code.js- Entry code is stored as a string variable (
__GAS_ENTRY_CODE__) and loaded at runtime viagetEntryCode() - Lazy chunks (
React.lazy()) are stored as__GAS_CHUNK_page_*__string variables, loaded on demand viagetPage(name) - Server entry is bundled via esbuild with exports hoisted to global scope
- HTML is transformed — webpack script tags are removed and replaced with a GAS chunk loader
Code.jsis generated withdoGet(),getPage(),getEntryCode(), and your server functionsappsscript.jsonis generated automatically
Options
createGASWebpackConfig(options)
| Option | Type | Default | Description |
|---|---|---|---|
clientRoot |
string |
"src/client" |
Root directory of client code |
outDir |
string |
"dist" |
Output directory |
devServerPort |
number |
3001 |
Port for the gas-react-core dev server proxy |
devPort |
number |
8080 |
Webpack dev server port |
aliases |
Record<string, string> |
{} |
Path aliases (e.g. { "@": "src" }) |
plugins |
unknown[] |
[] |
Extra webpack plugins |
appTitle |
string |
"GAS App" |
Title for the GAS web app |
serverEntry |
string |
— | Path to server entry (e.g. "src/server/index.ts") |
webpack |
object |
{} |
Additional webpack config overrides |
GASWebpackPlugin(options)
| Option | Type | Default | Description |
|---|---|---|---|
pagePrefix |
string |
"page_" |
Prefix for lazy-loaded page chunk names |
appTitle |
string |
"GAS App" |
Title for the web app |
serverEntry |
string |
— | Path to server entry file |
Development Mode
Set GAS_LOCAL=true to run in dev mode:
GAS_LOCAL=true npx webpack serveWhen createGASWebpackConfig detects dev mode:
- Runs a normal webpack dev server (HMR, source maps)
- Sets
window.__GAS_DEV_MODE__ = truesogas-react-core/clientroutes calls to your local dev server - Does not apply GAS transformations
Comparison with vite-plugin-gas-react
Both plugins produce identical GAS output. Choose based on your bundler:
| Feature | vite-plugin-gas-react | webpack-plugin-gas-react |
|---|---|---|
| Bundler | Vite | Webpack 5+ |
| Code splitting | ✅ | ✅ |
| Server bundling | ✅ (esbuild) | ✅ (esbuild) |
| HTML transform | ✅ | ✅ |
| Dev mode | ✅ | ✅ |
appsscript.json |
✅ | ✅ |
| Config helper | createGASViteConfig |
createGASWebpackConfig |
Full Example
webpack.config.mjs:
import { createGASWebpackConfig } from 'webpack-plugin-gas-react';
export default await createGASWebpackConfig({
clientRoot: 'src/client',
appTitle: 'My App',
serverEntry: 'src/server/index.ts',
});Server (src/server/index.ts):
import { DataStore, withCache, removeFromCache } from 'gas-react-core/server';
export function getUsers() {
return withCache({
cacheKey: 'all-users',
fetchFunction: () => DataStore.getAll('users'),
});
}
export function createUser(data: Record<string, unknown>) {
DataStore.insert('users', data);
removeFromCache('all-users');
return { success: true };
}Client (src/services/user-service.ts):
import { executeFn } from 'gas-react-core/client';
export const getUsers = () => executeFn<User[]>('getUsers');
export const createUser = (data: User) => executeFn('createUser', [data]);Build & deploy:
npx webpack
cd dist && clasp pushCombine with gas-react-core
This plugin handles build & deployment. Pair it with gas-react-core to unlock a complete client-server toolkit:
npm install gas-react-coreWhat gas-react-core adds
| Feature | Import | What it does |
|---|---|---|
executeFn |
gas-react-core/client |
Typed Promise wrapper around google.script.run — call server functions from React without callbacks |
DataStore |
gas-react-core/server |
Generic CRUD for Google Sheets (getAll, findBy, insert, update, remove) — each sheet is a table |
| Cache utilities | gas-react-core/server |
withCache, getFromCache, putInCache, removeFromCache — opt-in caching on top of GAS CacheService |
initApp |
gas-react-core/server |
One-liner doGet() setup with title and meta tags |
| Library mode | gas-react-core/client |
Publish your project as a GAS Library — executeFn routes calls through a proxy function automatically |
Example: full-stack GAS app
Server (src/server/index.ts):
import { DataStore, initCache, withCache, removeFromCache, initApp } from 'gas-react-core/server';
initCache({
'all-users': { key: 'all-users', duration: 300 },
}, true);
const app = initApp({ title: 'My App' });
export function doGet(e: unknown) { return app.doGet(e); }
export function getUsers() {
return withCache({
cacheKey: 'all-users',
fetchFunction: () => DataStore.getAll('users'),
});
}
export function createUser(data: Record<string, unknown>) {
DataStore.insert('users', data);
removeFromCache('all-users');
return { success: true };
}Client service (src/services/user-service.ts):
import { executeFn } from 'gas-react-core/client';
export const getUsers = () => executeFn<User[]>('getUsers');
export const createUser = (data: User) => executeFn('createUser', [data]);TL;DR —
webpack-plugin-gas-reactbuilds & deploys your React app to GAS.gas-react-coregives you the server utilities (Sheets CRUD, caching, app init) and a typed client bridge (executeFn) to tie it all together.
Local Testing with lite-gas-simulator
Develop and test your GAS server code locally — no clasp push required. lite-gas-simulator mocks all GAS globals (SpreadsheetApp, CacheService, PropertiesService, Logger, etc.) against local JSON files so your server functions run unmodified on your machine.
npm install lite-gas-simulator --save-devHow it fits in
Mock data — create a directory with one JSON file per sheet (row 1 = headers):
mock-data/ ├── users.json # [["id","name","email"],["1","Alice","alice@test.com"]] └── products.jsonRun server code locally — works seamlessly with
gas-react-core:import { createSimulator } from 'lite-gas-simulator'; import { DataStore } from 'gas-react-core/server'; const sim = createSimulator({ dataDir: './mock-data' }); sim.installGlobals(); // Your server functions work locally now const users = DataStore.getAll('users');
Companion dev server — when your webpack app runs with
GAS_LOCAL=true,executeFnsends requests tolocalhost:3001. Start the simulator's dev server to handle them:sim.startDevServer({ port: 3001, functions: { getUsers: () => DataStore.getAll('users'), createUser: (data) => DataStore.insert('users', data), }, });
Use in tests — install/remove globals in
beforeEach/afterEachfor isolated unit tests:import { createSimulator } from 'lite-gas-simulator'; import { DataStore } from 'gas-react-core/server'; let sim; beforeEach(() => { sim = createSimulator({ dataDir: './test-data' }); sim.installGlobals(); }); afterEach(() => sim.removeGlobals()); it('returns users', () => { expect(DataStore.getAll('users')).toHaveLength(2); });
TL;DR —
lite-gas-simulatorlets you run and test yourgas-react-coreserver code entirely offline. Mutations auto-persist to your JSON files, and the built-in dev server bridges your webpack frontend during local development.
License
MIT