@gasboost/vite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -0
- package/dist/analyzer.d.ts +6 -0
- package/dist/config.d.ts +3 -0
- package/dist/gasboost.d.ts +6 -0
- package/dist/globals.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +135 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# @gasboost/vite
|
|
2
|
+
|
|
3
|
+
`@gasboost/app` で構築したアプリケーションを Google Apps Script 向けにビルドするための Vite プラグインです。
|
|
4
|
+
|
|
5
|
+
アプリケーションの entry file を静的解析し、`AppsScript` に登録されたハンドラを検出して、Google Apps Script が認識するためのグローバル関数宣言を生成します。
|
|
6
|
+
|
|
7
|
+
## インストール
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @gasboost/app
|
|
11
|
+
pnpm add -D @gasboost/vite vite
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
npm:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @gasboost/app
|
|
18
|
+
npm install -D @gasboost/vite vite
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 使い方
|
|
22
|
+
|
|
23
|
+
まず通常の Gasboost アプリケーションを作成します。
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// src/main.ts
|
|
27
|
+
|
|
28
|
+
import { AppsScript } from "@gasboost/app";
|
|
29
|
+
|
|
30
|
+
const app = new AppsScript()
|
|
31
|
+
.get((request) => {
|
|
32
|
+
return HtmlService.createHtmlOutput("Hello");
|
|
33
|
+
})
|
|
34
|
+
.post((request) => {
|
|
35
|
+
return ContentService.createTextOutput(request.text());
|
|
36
|
+
})
|
|
37
|
+
.call("sum", (a: number, b: number) => a + b);
|
|
38
|
+
|
|
39
|
+
export default app;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Vite を設定します。
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// vite.config.ts
|
|
46
|
+
|
|
47
|
+
import { defineConfig } from "vite";
|
|
48
|
+
import { gasboost } from "@gasboost/vite";
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
plugins: [
|
|
52
|
+
gasboost({
|
|
53
|
+
entry: "src/main.ts",
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
通常通りビルドできます。
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
vite build
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## オプション
|
|
66
|
+
|
|
67
|
+
### `entry`
|
|
68
|
+
|
|
69
|
+
必須です。
|
|
70
|
+
|
|
71
|
+
`AppsScript` アプリケーションを定義している entry file のパスを指定します。
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
gasboost({
|
|
75
|
+
entry: "src/main.ts",
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
entry file には1つの `AppsScript` インスタンスを定義し、そのインスタンスを default export する必要があります。
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const app = new AppsScript();
|
|
83
|
+
|
|
84
|
+
export default app;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
チェーン形式の登録に対応しています。
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const app = new AppsScript()
|
|
91
|
+
.get(...)
|
|
92
|
+
.post(...)
|
|
93
|
+
.call("getUser", ...);
|
|
94
|
+
|
|
95
|
+
export default app;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
インスタンス生成後に登録する形式にも対応しています。
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const app = new AppsScript();
|
|
102
|
+
|
|
103
|
+
app.get(...);
|
|
104
|
+
app.call("getUser", ...);
|
|
105
|
+
|
|
106
|
+
export default app;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `envDir`
|
|
110
|
+
|
|
111
|
+
任意です。
|
|
112
|
+
|
|
113
|
+
Vite が環境変数ファイルを読み込むディレクトリを指定します。
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
gasboost({
|
|
117
|
+
entry: "src/main.ts",
|
|
118
|
+
envDir: "config",
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
例えば以下の構成を利用できます。
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
config/
|
|
126
|
+
├── .env
|
|
127
|
+
├── .env.development
|
|
128
|
+
└── .env.production
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 生成されるグローバル関数
|
|
132
|
+
|
|
133
|
+
Google Apps Script はトップレベルのグローバル関数を entry point として認識します。
|
|
134
|
+
|
|
135
|
+
例えば次のアプリケーションを定義した場合:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const app = new AppsScript()
|
|
139
|
+
.get(...)
|
|
140
|
+
.post(...)
|
|
141
|
+
.call("getUser", ...)
|
|
142
|
+
.call("sum", ...);
|
|
143
|
+
|
|
144
|
+
export default app;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
ビルド結果には以下に対応するグローバル関数宣言が生成されます。
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
function doGet() {}
|
|
151
|
+
function doPost() {}
|
|
152
|
+
function getUser() {}
|
|
153
|
+
function sum() {}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
これらの宣言は Google Apps Script に関数名を認識させるためのものです。
|
|
157
|
+
|
|
158
|
+
実際の dispatch 処理は行いません。
|
|
159
|
+
|
|
160
|
+
ハンドラの実体は `@gasboost/app` がランタイム上で登録します。
|
|
161
|
+
|
|
162
|
+
責務は次のように分離されています。
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
@gasboost/app
|
|
166
|
+
runtime registration
|
|
167
|
+
handler dispatch
|
|
168
|
+
globalThis implementation
|
|
169
|
+
|
|
170
|
+
@gasboost/vite
|
|
171
|
+
static analysis
|
|
172
|
+
GAS build configuration
|
|
173
|
+
global function declarations
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 静的解析
|
|
177
|
+
|
|
178
|
+
プラグインは指定された entry file を静的解析します。
|
|
179
|
+
|
|
180
|
+
対象となる `AppsScript` インスタンスに対する登録を検出します。
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
app.get(...);
|
|
184
|
+
app.post(...);
|
|
185
|
+
app.call("getUser", ...);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
別オブジェクトの同名メソッドは無視されます。
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
other.get(...);
|
|
192
|
+
other.call("something", ...);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### RPC 名
|
|
196
|
+
|
|
197
|
+
RPC 名は文字列リテラルで指定する必要があります。
|
|
198
|
+
|
|
199
|
+
対応:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
app.call("getUser", handler);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
非対応:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const name = "getUser";
|
|
209
|
+
|
|
210
|
+
app.call(name, handler);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
ビルド時に生成する GAS グローバル関数名を静的に確定するため、この制約があります。
|
|
214
|
+
|
|
215
|
+
### 重複登録
|
|
216
|
+
|
|
217
|
+
以下の曖昧な登録はエラーになります。
|
|
218
|
+
|
|
219
|
+
- GET ハンドラの重複
|
|
220
|
+
- POST ハンドラの重複
|
|
221
|
+
- RPC 名の重複
|
|
222
|
+
- entry 内に複数の `AppsScript` インスタンスが存在する場合
|
|
223
|
+
|
|
224
|
+
`AppsScript` インスタンスは default export されている必要があります。
|
|
225
|
+
|
|
226
|
+
## GAS 向けビルド設定
|
|
227
|
+
|
|
228
|
+
`gasboost()` が Google Apps Script 向けの Vite build configuration を提供します。
|
|
229
|
+
|
|
230
|
+
現在は以下の設定を利用します。
|
|
231
|
+
|
|
232
|
+
- target: ECMAScript 2019
|
|
233
|
+
- output format: CommonJS
|
|
234
|
+
- output directory: `dist`
|
|
235
|
+
- `entry` で指定されたファイルを build input として利用
|
|
236
|
+
|
|
237
|
+
利用側の Vite config で GAS 固有の build setting を重複して定義する必要はありません。
|
|
238
|
+
|
|
239
|
+
## 環境変数
|
|
240
|
+
|
|
241
|
+
環境変数は Vite 標準の仕組みを利用します。
|
|
242
|
+
|
|
243
|
+
例えば:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
config/.env
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```env
|
|
250
|
+
VITE_API_URL=https://example.com
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`envDir` を設定します。
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
gasboost({
|
|
257
|
+
entry: "src/main.ts",
|
|
258
|
+
envDir: "config",
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
アプリケーションから通常通り参照できます。
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const apiUrl = import.meta.env.VITE_API_URL;
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
mode ごとの環境変数ファイルにも対応します。
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
vite build --mode production
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`envDir: "config"` の場合、例えば以下が読み込まれます。
|
|
275
|
+
|
|
276
|
+
```text
|
|
277
|
+
config/.env.production
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 責務
|
|
281
|
+
|
|
282
|
+
`@gasboost/vite` はビルド時の処理のみを担当します。
|
|
283
|
+
|
|
284
|
+
主な責務:
|
|
285
|
+
|
|
286
|
+
- `AppsScript` 登録内容の静的解析
|
|
287
|
+
- Google Apps Script 向け Vite configuration
|
|
288
|
+
- GAS グローバル関数宣言の生成
|
|
289
|
+
- Vite 環境変数との統合
|
|
290
|
+
|
|
291
|
+
以下は担当しません。
|
|
292
|
+
|
|
293
|
+
- GET ハンドラの実行
|
|
294
|
+
- POST ハンドラの実行
|
|
295
|
+
- RPC dispatch
|
|
296
|
+
- ハンドラ実体のランタイム登録
|
|
297
|
+
- Google Apps Script API 自体の抽象化
|
|
298
|
+
|
|
299
|
+
これらの runtime responsibility は `@gasboost/app` が担当します。
|
|
300
|
+
|
|
301
|
+
## 現在の制約
|
|
302
|
+
|
|
303
|
+
Analyzer は、意図的にシンプルな entry file 構造のみを対象としています。
|
|
304
|
+
|
|
305
|
+
以下のような alias を介した default export は現在対象外です。
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
const app = new AppsScript();
|
|
309
|
+
const exported = app;
|
|
310
|
+
|
|
311
|
+
export default exported;
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
別関数の内部に登録処理を隠す形式も対象外です。
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
registerHandlers(app);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
別ファイルに登録処理を分散する形式も現在対象外です。
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { registerHandlers } from "./handlers";
|
|
324
|
+
|
|
325
|
+
registerHandlers(app);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
登録内容を entry file から静的に確定できるようにすることで、生成する GAS グローバル関数を決定的にしています。
|
|
329
|
+
|
|
330
|
+
## 関連パッケージ
|
|
331
|
+
|
|
332
|
+
アプリケーションの runtime と handler registration には `@gasboost/app` を利用してください。
|
package/dist/config.d.ts
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { gasboost, type GasboostOptions } from "./gasboost";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import e from "node:fs";
|
|
2
|
+
import t from "node:path";
|
|
3
|
+
import { isCallExpression as n, isIdentifier as r, isNewExpression as i, isPropertyAccessExpression as a, isStringLiteral as o, isVariableDeclaration as s } from "typescript/unstable/ast/is";
|
|
4
|
+
import { createVirtualFileSystem as c } from "typescript/unstable/fs";
|
|
5
|
+
import { API as l } from "typescript/unstable/sync";
|
|
6
|
+
//#region src/analyzer.ts
|
|
7
|
+
function u(r) {
|
|
8
|
+
let i = t.resolve(r);
|
|
9
|
+
if (!e.existsSync(i)) throw Error(`Entry file not found: ${i}`);
|
|
10
|
+
let s = d(i, e.readFileSync(i, "utf8")), c = f(s);
|
|
11
|
+
if (c.length === 0) throw Error("AppsScript instance not found.");
|
|
12
|
+
if (c.length > 1) throw Error("Multiple AppsScript instances found in entry.");
|
|
13
|
+
let l = c[0];
|
|
14
|
+
g(s, l);
|
|
15
|
+
let u = {
|
|
16
|
+
hasGet: !1,
|
|
17
|
+
hasPost: !1,
|
|
18
|
+
calls: []
|
|
19
|
+
}, p = /* @__PURE__ */ new Set();
|
|
20
|
+
function m(e) {
|
|
21
|
+
if (!a(e.expression)) return;
|
|
22
|
+
let t = e.expression, n = t.name.text;
|
|
23
|
+
if (!h(t.expression, l)) return;
|
|
24
|
+
if (n === "get") {
|
|
25
|
+
if (u.hasGet) throw Error("Duplicate GET handler registration.");
|
|
26
|
+
u.hasGet = !0;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (n === "post") {
|
|
30
|
+
if (u.hasPost) throw Error("Duplicate POST handler registration.");
|
|
31
|
+
u.hasPost = !0;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (n !== "call") return;
|
|
35
|
+
let r = e.arguments[0];
|
|
36
|
+
if (!r) throw Error(".call() requires a function name.");
|
|
37
|
+
if (!o(r)) throw Error(".call() function name must be a string literal.");
|
|
38
|
+
if (p.has(r.text)) throw Error(`Duplicate RPC registration: "${r.text}".`);
|
|
39
|
+
p.add(r.text), u.calls.unshift(r.text);
|
|
40
|
+
}
|
|
41
|
+
function _(e) {
|
|
42
|
+
n(e) && m(e), e.forEachChild(_);
|
|
43
|
+
}
|
|
44
|
+
return _(s), u;
|
|
45
|
+
}
|
|
46
|
+
function d(e, t) {
|
|
47
|
+
let n = "/entry.ts", r = "/tsconfig.json", i = c({
|
|
48
|
+
[r]: JSON.stringify({ files: [n] }),
|
|
49
|
+
[n]: t
|
|
50
|
+
}), a = new l({
|
|
51
|
+
cwd: "/",
|
|
52
|
+
fs: i
|
|
53
|
+
}).updateSnapshot({ openProject: r }).getProject(r);
|
|
54
|
+
if (!a) throw Error("Failed to create TypeScript project.");
|
|
55
|
+
let o = a.program.getSourceFile(n);
|
|
56
|
+
if (!o) throw Error(`Failed to parse entry: ${e}`);
|
|
57
|
+
return o;
|
|
58
|
+
}
|
|
59
|
+
function f(e) {
|
|
60
|
+
let t = [];
|
|
61
|
+
function n(e) {
|
|
62
|
+
s(e) && p(e) && t.push(e.name.text), e.forEachChild(n);
|
|
63
|
+
}
|
|
64
|
+
return n(e), t;
|
|
65
|
+
}
|
|
66
|
+
function p(e) {
|
|
67
|
+
return !r(e.name) || !e.initializer ? !1 : m(e.initializer);
|
|
68
|
+
}
|
|
69
|
+
function m(e) {
|
|
70
|
+
return i(e) && r(e.expression) && e.expression.text === "AppsScript" ? !0 : n(e) && a(e.expression) ? m(e.expression.expression) : !1;
|
|
71
|
+
}
|
|
72
|
+
function h(e, t) {
|
|
73
|
+
return r(e) && e.text === t || i(e) && r(e.expression) && e.expression.text === "AppsScript" ? !0 : n(e) && a(e.expression) ? h(e.expression.expression, t) : !1;
|
|
74
|
+
}
|
|
75
|
+
function g(e, t) {
|
|
76
|
+
let n = !1;
|
|
77
|
+
for (let r of e.statements) if (r.getText() === `export default ${t};`) {
|
|
78
|
+
n = !0;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (!n) throw Error("AppsScript instance must be default exported.");
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/config.ts
|
|
85
|
+
function _(e) {
|
|
86
|
+
let n = t.resolve(e.entry);
|
|
87
|
+
return {
|
|
88
|
+
envDir: e.envDir,
|
|
89
|
+
build: {
|
|
90
|
+
target: "es2019",
|
|
91
|
+
outDir: "dist",
|
|
92
|
+
emptyOutDir: !1,
|
|
93
|
+
rollupOptions: {
|
|
94
|
+
input: n,
|
|
95
|
+
output: {
|
|
96
|
+
format: "cjs",
|
|
97
|
+
entryFileNames: t.basename(n, t.extname(n)) + ".js"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/globals.ts
|
|
105
|
+
var v = /* @__PURE__ */ new Set(["doGet", "doPost"]);
|
|
106
|
+
function y(e) {
|
|
107
|
+
if (v.has(e)) throw Error(`RPC name "${e}" is reserved by Google Apps Script.`);
|
|
108
|
+
if (!/^[$A-Z_a-z][$\w]*$/u.test(e)) throw Error(`RPC name "${e}" is not a valid JavaScript identifier.`);
|
|
109
|
+
}
|
|
110
|
+
function b(e) {
|
|
111
|
+
let t = [];
|
|
112
|
+
e.hasGet && t.push("function doGet() {}"), e.hasPost && t.push("function doPost() {}");
|
|
113
|
+
for (let n of e.calls) y(n), t.push(`function ${n}() {}`);
|
|
114
|
+
return t.join("\n");
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/gasboost.ts
|
|
118
|
+
function x(e) {
|
|
119
|
+
let t;
|
|
120
|
+
return {
|
|
121
|
+
name: "gasboost",
|
|
122
|
+
config() {
|
|
123
|
+
return _(e);
|
|
124
|
+
},
|
|
125
|
+
buildStart() {
|
|
126
|
+
t = u(e.entry);
|
|
127
|
+
},
|
|
128
|
+
generateBundle(e, n) {
|
|
129
|
+
let r = b(t);
|
|
130
|
+
for (let e of Object.values(n)) e.type === "chunk" && e.isEntry && (e.code = `${r}\n\n${e.code}`);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { x as gasboost };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gasboost/vite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Vite plugin for building @gasboost/app applications for Google Apps Script.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"google-apps-script",
|
|
7
|
+
"gas",
|
|
8
|
+
"vite",
|
|
9
|
+
"typescript",
|
|
10
|
+
"plugin",
|
|
11
|
+
"build",
|
|
12
|
+
"bundler",
|
|
13
|
+
"serverless",
|
|
14
|
+
"gasboost"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/gasboost/app.git",
|
|
20
|
+
"directory": "packages/vite"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"default": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"vite": "^8.2.2"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"typescript": "^7.0.2"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"build": "rm -rf dist && vite build --config vite.package.config.ts && tsc -p tsconfig.build.json"
|
|
45
|
+
}
|
|
46
|
+
}
|