npm package: @gas-plugin/unplugin
Description: Universal bundler plugin for Google Apps Script projects (Vite, Rollup, Rolldown, webpack, esbuild, Bun)
Author: wakita181009
Latest version: 0.1.2

# @gas-plugin/unplugin

[![CI](https://github.com/wakita181009/gas-plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/wakita181009/gas-plugin/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wakita181009/gas-plugin/graph/badge.svg)](https://codecov.io/gh/wakita181009/gas-plugin)
[![npm Version](https://img.shields.io/npm/v/@gas-plugin/unplugin.svg)](https://www.npmjs.com/package/@gas-plugin/unplugin)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

A universal bundler plugin for Google Apps Script (GAS) projects. Works with **Vite**, **Rollup**, **Rolldown**, **webpack**, **esbuild**, and **Bun**.

## What It Does

Google Apps Script requires plain top-level function declarations — no `export` keywords, no ES module syntax. This plugin bridges the gap between modern bundler output and what GAS expects:

- **Strips `export` keywords** from function/class/variable declarations
- **Removes `export { ... }` blocks** generated by bundlers
- **Prevents tree-shaking** of GAS entry points (e.g., `onOpen`, `doGet`)
- **Copies `appsscript.json`** manifest to the output directory
- **Copies additional files** (HTML, JSON, etc.) via glob patterns

## Install

```bash
# npm
npm install -D @gas-plugin/unplugin

# yarn
yarn add -D @gas-plugin/unplugin

# pnpm
pnpm add -D @gas-plugin/unplugin
```

## Usage

### Vite

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

export default defineConfig({
  plugins: [gasPlugin()],
  build: {
    lib: {
      entry: "src/main.ts",
      formats: ["es"],
      fileName: () => "Code.js",
    },
  },
});
```

### Rollup

```ts
// rollup.config.js
import gasPlugin from "@gas-plugin/unplugin/rollup";

export default {
  input: "src/main.ts",
  output: { dir: "dist", format: "es" },
  plugins: [gasPlugin()],
};
```

### webpack

```js
// webpack.config.js
const gasPlugin = require("@gas-plugin/unplugin/webpack").default;

module.exports = {
  entry: "./src/main.ts",
  output: { path: __dirname + "/dist" },
  plugins: [gasPlugin()],
};
```

### esbuild

```ts
import gasPlugin from "@gas-plugin/unplugin/esbuild";
import { build } from "esbuild";

await build({
  entryPoints: ["src/main.ts"],
  outdir: "dist",
  bundle: true,
  format: "esm",
  plugins: [gasPlugin()],
});
```

### Rolldown

```ts
// rolldown.config.mjs
import gasPlugin from "@gas-plugin/unplugin/rolldown";

export default {
  input: "src/main.ts",
  output: { dir: "dist", format: "es" },
  plugins: [gasPlugin()],
};
```

### Bun

```ts
import gasPlugin from "@gas-plugin/unplugin/bun";

await Bun.build({
  entrypoints: ["src/main.ts"],
  outdir: "dist",
  plugins: [gasPlugin()],
});
```

## Options

```ts
interface GasPluginOptions {
  /**
   * Path to appsscript.json manifest file.
   * Resolved relative to the project root.
   * @default "appsscript.json"
   */
  manifest?: string;

  /**
   * Glob patterns for additional files to copy flat to the output directory.
   * Files are copied without subdirectory structure (basename only).
   * Duplicate basenames trigger a warning; the first match wins.
   * @default []
   */
  include?: string[];

  /**
   * Function names to explicitly protect from tree-shaking.
   * These functions will be kept as top-level declarations in the output,
   * even if they are not exported from the entry point.
   * @default []
   */
  globals?: string[];

  /**
   * When true (default), exported functions are automatically added to the
   * tree-shake protection list. When false, only functions explicitly listed
   * in `globals` are protected.
   * @default true
   */
  autoGlobals?: boolean;
}
```

## Examples

### Basic GAS Project

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

export default defineConfig({
  plugins: [gasPlugin()],
  build: {
    lib: {
      entry: "src/main.ts",
      formats: ["es"],
      fileName: () => "Code.js",
    },
  },
});
```

### GAS Web App with HTML

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

export default defineConfig({
  plugins: [
    gasPlugin({
      include: ["src/**/*.html"],
      globals: ["getData", "saveData"],
    }),
  ],
  build: {
    lib: {
      entry: "src/main.ts",
      formats: ["es"],
      fileName: () => "Code.js",
    },
  },
});
```

## How It Works

1. **Transform phase**: Detects exported function declarations and injects tree-shake protection markers (`globalThis.__gas_keep__`) to prevent bundlers from removing GAS entry points.

2. **Post-process phase**: After bundling, strips all `export` keywords and `export { ... }` blocks, removes the tree-shake protection markers, and normalizes trailing newlines.

3. **File copy phase**: Copies `appsscript.json` and any files matching `include` patterns to the output directory.

## Compatibility

| Bundler  | Version        |
| -------- | -------------- |
| Vite     | >= 5.0.0       |
| Rollup   | >= 4.0.0       |
| Rolldown | >= 1.0.0-rc.1  |
| webpack  | >= 5.0.0       |
| esbuild  | >= 0.17.0      |
| Bun      | latest         |

**Node.js**: >= 20

## License

MIT