@gasboost/vite 0.1.0 → 1.0.1
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/LICENSE +21 -0
- package/README.md +458 -137
- package/dist/build.d.ts +3 -0
- package/dist/dev.d.ts +2 -0
- package/dist/gasboost.d.ts +4 -1
- package/dist/index.js +89 -3
- package/package.json +6 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gasboost
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# @gasboost/vite
|
|
2
2
|
|
|
3
|
-
`@gasboost/app`
|
|
3
|
+
`@gasboost/app` で構築した Google Apps Script アプリケーションを、Vite でビルド・ローカル開発するためのプラグインです。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@gasboost/vite` は用途ごとに2つの Vite Plugin を提供します。
|
|
6
|
+
|
|
7
|
+
- `build` — Google Apps Script 向けの production build
|
|
8
|
+
- `dev` — ローカル開発時の RPC 実行
|
|
6
9
|
|
|
7
10
|
## インストール
|
|
8
11
|
|
|
@@ -18,119 +21,175 @@ npm install @gasboost/app
|
|
|
18
21
|
npm install -D @gasboost/vite vite
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
##
|
|
24
|
+
## Quick Start
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
まず `@gasboost/app` でバックエンドを定義します。
|
|
24
27
|
|
|
25
28
|
```ts
|
|
26
|
-
// src/
|
|
29
|
+
// src/server.ts
|
|
27
30
|
|
|
28
31
|
import { AppsScript } from "@gasboost/app";
|
|
29
32
|
|
|
30
33
|
const app = new AppsScript()
|
|
31
|
-
.get((
|
|
34
|
+
.get(() => {
|
|
32
35
|
return HtmlService.createHtmlOutput("Hello");
|
|
33
36
|
})
|
|
34
37
|
.post((request) => {
|
|
35
38
|
return ContentService.createTextOutput(request.text());
|
|
36
39
|
})
|
|
37
|
-
.call("sum", (a: number, b: number) => a + b)
|
|
40
|
+
.call("sum", (a: number, b: number) => a + b)
|
|
41
|
+
.call("getUser", async (id: string) => ({
|
|
42
|
+
id,
|
|
43
|
+
name: "Taro",
|
|
44
|
+
}));
|
|
38
45
|
|
|
39
46
|
export default app;
|
|
40
47
|
```
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
`gasboost()` から `build` と `dev` を取得し、1つの `vite.config.ts` の中で mode に応じて使い分けます。
|
|
50
|
+
|
|
51
|
+
frontend と backend は成果物と build graph が異なるため、同時に1つの Vite build へ混在させません。
|
|
43
52
|
|
|
44
53
|
```ts
|
|
45
54
|
// vite.config.ts
|
|
46
55
|
|
|
56
|
+
import react from "@vitejs/plugin-react";
|
|
47
57
|
import { defineConfig } from "vite";
|
|
58
|
+
import { viteSingleFile } from "vite-plugin-singlefile";
|
|
48
59
|
import { gasboost } from "@gasboost/vite";
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
entry: "src/main.ts",
|
|
54
|
-
}),
|
|
55
|
-
],
|
|
61
|
+
const { build, dev } = gasboost({
|
|
62
|
+
entry: "src/server.ts",
|
|
63
|
+
envDir: ".env",
|
|
56
64
|
});
|
|
57
|
-
```
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
export default defineConfig(({ mode }) => {
|
|
67
|
+
if (mode === "server") {
|
|
68
|
+
return {
|
|
69
|
+
plugins: [build],
|
|
70
|
+
build: {
|
|
71
|
+
outDir: "dist",
|
|
72
|
+
emptyOutDir: false,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
plugins: [react(), viteSingleFile(), dev],
|
|
79
|
+
build: {
|
|
80
|
+
outDir: "dist",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
```
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
この例では、frontend build と backend build を mode で分離します。
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"scripts": {
|
|
91
|
+
"dev": "vite",
|
|
92
|
+
"build:client": "vite build --mode client",
|
|
93
|
+
"build:server": "vite build --mode server",
|
|
94
|
+
"build": "pnpm build:client && pnpm build:server"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
63
97
|
```
|
|
64
98
|
|
|
65
|
-
|
|
99
|
+
```text
|
|
100
|
+
vite
|
|
101
|
+
↓
|
|
102
|
+
gasboost:dev
|
|
66
103
|
|
|
67
|
-
|
|
104
|
+
vite build --mode client
|
|
105
|
+
↓
|
|
106
|
+
frontend build
|
|
68
107
|
|
|
69
|
-
|
|
108
|
+
vite build --mode server
|
|
109
|
+
↓
|
|
110
|
+
gasboost:build
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
frontend 側の React、single-file 化などの設定は利用側プロジェクトの責務です。
|
|
70
114
|
|
|
71
|
-
|
|
115
|
+
## gasboost()
|
|
72
116
|
|
|
73
117
|
```ts
|
|
74
|
-
gasboost({
|
|
75
|
-
entry: "src/
|
|
118
|
+
const { build, dev } = gasboost({
|
|
119
|
+
entry: "src/server.ts",
|
|
120
|
+
envDir: "config",
|
|
76
121
|
});
|
|
77
122
|
```
|
|
78
123
|
|
|
79
|
-
|
|
124
|
+
戻り値:
|
|
80
125
|
|
|
81
126
|
```ts
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
127
|
+
{
|
|
128
|
+
build: Plugin;
|
|
129
|
+
dev: Plugin;
|
|
130
|
+
}
|
|
85
131
|
```
|
|
86
132
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
const app = new AppsScript()
|
|
91
|
-
.get(...)
|
|
92
|
-
.post(...)
|
|
93
|
-
.call("getUser", ...);
|
|
133
|
+
### `entry`
|
|
94
134
|
|
|
95
|
-
|
|
96
|
-
```
|
|
135
|
+
必須です。
|
|
97
136
|
|
|
98
|
-
|
|
137
|
+
`AppsScript` を定義して default export している entry file を指定します。
|
|
99
138
|
|
|
100
139
|
```ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
app.call("getUser", ...);
|
|
105
|
-
|
|
106
|
-
export default app;
|
|
140
|
+
gasboost({
|
|
141
|
+
entry: "src/server.ts",
|
|
142
|
+
});
|
|
107
143
|
```
|
|
108
144
|
|
|
109
145
|
### `envDir`
|
|
110
146
|
|
|
111
147
|
任意です。
|
|
112
148
|
|
|
113
|
-
Vite が環境変数ファイルを読み込むディレクトリを指定します。
|
|
149
|
+
build 時に Vite が環境変数ファイルを読み込むディレクトリを指定します。
|
|
114
150
|
|
|
115
151
|
```ts
|
|
116
152
|
gasboost({
|
|
117
|
-
entry: "src/
|
|
153
|
+
entry: "src/server.ts",
|
|
118
154
|
envDir: "config",
|
|
119
155
|
});
|
|
120
156
|
```
|
|
121
157
|
|
|
122
|
-
|
|
158
|
+
---
|
|
123
159
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
160
|
+
# Build Plugin
|
|
161
|
+
|
|
162
|
+
`build` は `vite build` のときだけ動作し、GAS backend build を担当します。
|
|
163
|
+
|
|
164
|
+
frontend / backend を同じ `vite.config.ts` で扱う場合は、server 用 mode のときだけ `plugins` に含めてください。
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const { build } = gasboost({
|
|
168
|
+
entry: "src/server.ts",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export default defineConfig(({ mode }) => {
|
|
172
|
+
if (mode === "server") {
|
|
173
|
+
return {
|
|
174
|
+
plugins: [build],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
plugins: [],
|
|
180
|
+
};
|
|
181
|
+
});
|
|
129
182
|
```
|
|
130
183
|
|
|
131
|
-
|
|
184
|
+
`build` plugin は次の処理を担当します。
|
|
132
185
|
|
|
133
|
-
|
|
186
|
+
- entry file の静的解析
|
|
187
|
+
- `AppsScript` に登録された GET / POST / RPC の検出
|
|
188
|
+
- GAS が認識するグローバル関数宣言の生成
|
|
189
|
+
- GAS 向け Vite build configuration
|
|
190
|
+
- 環境変数の読み込み
|
|
191
|
+
|
|
192
|
+
## グローバル関数の生成
|
|
134
193
|
|
|
135
194
|
例えば次のアプリケーションを定義した場合:
|
|
136
195
|
|
|
@@ -144,7 +203,7 @@ const app = new AppsScript()
|
|
|
144
203
|
export default app;
|
|
145
204
|
```
|
|
146
205
|
|
|
147
|
-
|
|
206
|
+
build plugin は GAS が認識するためのグローバル関数宣言を生成します。
|
|
148
207
|
|
|
149
208
|
```js
|
|
150
209
|
function doGet() {}
|
|
@@ -153,56 +212,51 @@ function getUser() {}
|
|
|
153
212
|
function sum() {}
|
|
154
213
|
```
|
|
155
214
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
実際の dispatch 処理は行いません。
|
|
159
|
-
|
|
160
|
-
ハンドラの実体は `@gasboost/app` がランタイム上で登録します。
|
|
215
|
+
これらは関数名を Google Apps Script に認識させるための宣言です。
|
|
161
216
|
|
|
162
|
-
|
|
217
|
+
実際のハンドラ登録と dispatch は `@gasboost/app` がランタイム上で行います。
|
|
163
218
|
|
|
164
219
|
```text
|
|
165
|
-
@gasboost/app
|
|
166
|
-
runtime registration
|
|
167
|
-
handler dispatch
|
|
168
|
-
globalThis implementation
|
|
169
|
-
|
|
170
220
|
@gasboost/vite
|
|
171
221
|
static analysis
|
|
172
|
-
GAS build configuration
|
|
173
222
|
global function declarations
|
|
223
|
+
↓
|
|
224
|
+
@gasboost/app
|
|
225
|
+
runtime registration
|
|
226
|
+
dispatch
|
|
174
227
|
```
|
|
175
228
|
|
|
176
229
|
## 静的解析
|
|
177
230
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
対象となる `AppsScript` インスタンスに対する登録を検出します。
|
|
231
|
+
entry file には1つの `AppsScript` インスタンスを定義し、default export します。
|
|
181
232
|
|
|
182
233
|
```ts
|
|
234
|
+
const app = new AppsScript();
|
|
235
|
+
|
|
183
236
|
app.get(...);
|
|
184
|
-
app.post(...);
|
|
185
237
|
app.call("getUser", ...);
|
|
238
|
+
|
|
239
|
+
export default app;
|
|
186
240
|
```
|
|
187
241
|
|
|
188
|
-
|
|
242
|
+
チェーン形式にも対応しています。
|
|
189
243
|
|
|
190
244
|
```ts
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
245
|
+
const app = new AppsScript()
|
|
246
|
+
.get(...)
|
|
247
|
+
.post(...)
|
|
248
|
+
.call("getUser", ...);
|
|
194
249
|
|
|
195
|
-
|
|
250
|
+
export default app;
|
|
251
|
+
```
|
|
196
252
|
|
|
197
253
|
RPC 名は文字列リテラルで指定する必要があります。
|
|
198
254
|
|
|
199
|
-
対応:
|
|
200
|
-
|
|
201
255
|
```ts
|
|
202
256
|
app.call("getUser", handler);
|
|
203
257
|
```
|
|
204
258
|
|
|
205
|
-
|
|
259
|
+
次のような動的な名前は静的解析の対象外です。
|
|
206
260
|
|
|
207
261
|
```ts
|
|
208
262
|
const name = "getUser";
|
|
@@ -210,123 +264,390 @@ const name = "getUser";
|
|
|
210
264
|
app.call(name, handler);
|
|
211
265
|
```
|
|
212
266
|
|
|
213
|
-
|
|
267
|
+
build 時に GAS のグローバル関数名を確定する必要があるためです。
|
|
214
268
|
|
|
215
|
-
|
|
269
|
+
## 検証
|
|
216
270
|
|
|
217
|
-
|
|
271
|
+
静的解析時には、曖昧なアプリケーション定義をエラーとして扱います。
|
|
218
272
|
|
|
219
273
|
- GET ハンドラの重複
|
|
220
274
|
- POST ハンドラの重複
|
|
221
275
|
- RPC 名の重複
|
|
222
|
-
- entry
|
|
223
|
-
|
|
224
|
-
`AppsScript` インスタンスは default export されている必要があります。
|
|
225
|
-
|
|
226
|
-
## GAS 向けビルド設定
|
|
276
|
+
- entry 内の複数 `AppsScript` インスタンス
|
|
277
|
+
- default export されていない `AppsScript`
|
|
227
278
|
|
|
228
|
-
|
|
279
|
+
## GAS 向け build configuration
|
|
229
280
|
|
|
230
|
-
|
|
281
|
+
現在の build 設定:
|
|
231
282
|
|
|
232
283
|
- target: ECMAScript 2019
|
|
233
284
|
- output format: CommonJS
|
|
234
285
|
- output directory: `dist`
|
|
235
|
-
- `entry`
|
|
236
|
-
|
|
237
|
-
利用側の Vite config で GAS 固有の build setting を重複して定義する必要はありません。
|
|
286
|
+
- `entry` を build input として利用
|
|
238
287
|
|
|
239
288
|
## 環境変数
|
|
240
289
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
例えば:
|
|
290
|
+
Vite 標準の環境変数機構を利用します。
|
|
244
291
|
|
|
245
292
|
```text
|
|
246
|
-
config
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
VITE_API_URL=https://example.com
|
|
293
|
+
config/
|
|
294
|
+
├── .env
|
|
295
|
+
├── .env.development
|
|
296
|
+
└── .env.production
|
|
251
297
|
```
|
|
252
298
|
|
|
253
|
-
`envDir` を設定します。
|
|
254
|
-
|
|
255
299
|
```ts
|
|
256
|
-
gasboost({
|
|
257
|
-
entry: "src/
|
|
300
|
+
const { build } = gasboost({
|
|
301
|
+
entry: "src/server.ts",
|
|
258
302
|
envDir: "config",
|
|
259
303
|
});
|
|
260
304
|
```
|
|
261
305
|
|
|
262
|
-
|
|
306
|
+
アプリケーションでは通常の Vite と同様に参照できます。
|
|
263
307
|
|
|
264
308
|
```ts
|
|
265
309
|
const apiUrl = import.meta.env.VITE_API_URL;
|
|
266
310
|
```
|
|
267
311
|
|
|
268
|
-
|
|
312
|
+
---
|
|
269
313
|
|
|
270
|
-
|
|
271
|
-
|
|
314
|
+
# Dev Plugin
|
|
315
|
+
|
|
316
|
+
`dev` は Vite Dev Server 上でのみ動作します。
|
|
317
|
+
|
|
318
|
+
frontend の開発用 Vite config に追加して利用します。
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
const { dev } = gasboost({
|
|
322
|
+
entry: "src/server.ts",
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
export default defineConfig({
|
|
326
|
+
plugins: [dev],
|
|
327
|
+
});
|
|
272
328
|
```
|
|
273
329
|
|
|
274
|
-
`
|
|
330
|
+
`dev` plugin は、GAS にデプロイしなくてもローカル環境から `AppsScript` の RPC を実行できるエンドポイントを Vite Dev Server に追加します。
|
|
331
|
+
|
|
332
|
+
## Local RPC
|
|
333
|
+
|
|
334
|
+
ローカル RPC のエンドポイントは次の形式です。
|
|
275
335
|
|
|
276
336
|
```text
|
|
277
|
-
|
|
337
|
+
POST /__gasboost/{rpcName}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
例えば、
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
const app = new AppsScript().call("sum", (a: number, b: number) => a + b);
|
|
344
|
+
|
|
345
|
+
export default app;
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
に対して、
|
|
349
|
+
|
|
350
|
+
```http
|
|
351
|
+
POST /__gasboost/sum
|
|
352
|
+
Content-Type: application/json
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"args": [1, 2]
|
|
358
|
+
}
|
|
278
359
|
```
|
|
279
360
|
|
|
280
|
-
|
|
361
|
+
を送信すると、内部では次の dispatch が実行されます。
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
app.dispatch("sum", 1, 2);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
レスポンス:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
3
|
|
371
|
+
```
|
|
281
372
|
|
|
282
|
-
|
|
373
|
+
## RPC Request
|
|
283
374
|
|
|
284
|
-
|
|
375
|
+
Request Body は次の形式です。
|
|
285
376
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
377
|
+
```ts
|
|
378
|
+
{
|
|
379
|
+
args: unknown[];
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
例えば複数の引数を持つ RPC:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
app.call(
|
|
387
|
+
"example",
|
|
388
|
+
(id: number, name: string, active: boolean, options: { value: number }) => {
|
|
389
|
+
// ...
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
```
|
|
290
393
|
|
|
291
|
-
|
|
394
|
+
に対して、
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"args": [
|
|
399
|
+
1,
|
|
400
|
+
"Taro",
|
|
401
|
+
true,
|
|
402
|
+
{
|
|
403
|
+
"value": 4
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
```
|
|
292
408
|
|
|
293
|
-
|
|
294
|
-
- POST ハンドラの実行
|
|
295
|
-
- RPC dispatch
|
|
296
|
-
- ハンドラ実体のランタイム登録
|
|
297
|
-
- Google Apps Script API 自体の抽象化
|
|
409
|
+
のように送信できます。
|
|
298
410
|
|
|
299
|
-
|
|
411
|
+
### 引数なし RPC
|
|
300
412
|
|
|
301
|
-
|
|
413
|
+
Request Body が空の場合は、引数なし RPC として扱われます。
|
|
302
414
|
|
|
303
|
-
|
|
415
|
+
```http
|
|
416
|
+
POST /__gasboost/noArgs
|
|
417
|
+
```
|
|
304
418
|
|
|
305
|
-
|
|
419
|
+
内部では次のように dispatch されます。
|
|
306
420
|
|
|
307
421
|
```ts
|
|
308
|
-
|
|
309
|
-
|
|
422
|
+
app.dispatch("noArgs");
|
|
423
|
+
```
|
|
310
424
|
|
|
311
|
-
|
|
425
|
+
明示的に送る場合は次でも構いません。
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"args": []
|
|
430
|
+
}
|
|
312
431
|
```
|
|
313
432
|
|
|
314
|
-
|
|
433
|
+
## RPC Response
|
|
434
|
+
|
|
435
|
+
`@gasboost/app` の `dispatch()` が返す `AppsScriptResponse.contents` を、そのまま HTTP Response Body として返します。
|
|
436
|
+
|
|
437
|
+
例えば、
|
|
315
438
|
|
|
316
439
|
```ts
|
|
317
|
-
|
|
440
|
+
app.call("getUser", () => ({
|
|
441
|
+
id: 1,
|
|
442
|
+
name: "Taro",
|
|
443
|
+
}));
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
に対するレスポンスは:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"id": 1,
|
|
451
|
+
"name": "Taro"
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
となります。
|
|
456
|
+
|
|
457
|
+
Response の Content-Type は:
|
|
458
|
+
|
|
459
|
+
```text
|
|
460
|
+
application/json; charset=utf-8
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
です。
|
|
464
|
+
|
|
465
|
+
## Async RPC
|
|
466
|
+
|
|
467
|
+
非同期 RPC にも対応しています。
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
app.call("loadUser", async (id: string) => {
|
|
471
|
+
const user = await loadUser(id);
|
|
472
|
+
|
|
473
|
+
return user;
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
dev plugin は `dispatch()` の完了を待ってからレスポンスを返します。
|
|
478
|
+
|
|
479
|
+
## 開発中の module loading
|
|
480
|
+
|
|
481
|
+
RPC リクエストごとに、指定された `entry` を Vite の `ssrLoadModule()` で読み込みます。
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
POST /__gasboost/sum
|
|
485
|
+
↓
|
|
486
|
+
ssrLoadModule("src/server.ts")
|
|
487
|
+
↓
|
|
488
|
+
AppsScript.dispatch("sum", ...)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
そのため、Vite Dev Server 上の最新のサーバーコードを利用して RPC を実行できます。
|
|
492
|
+
|
|
493
|
+
## Error Response
|
|
494
|
+
|
|
495
|
+
### 不正な Request Body
|
|
496
|
+
|
|
497
|
+
JSON として不正な body や、`args` が配列でない body は `400` を返します。
|
|
498
|
+
|
|
499
|
+
```json
|
|
500
|
+
{
|
|
501
|
+
"args": "invalid"
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
レスポンス例:
|
|
506
|
+
|
|
507
|
+
```json
|
|
508
|
+
{
|
|
509
|
+
"error": {
|
|
510
|
+
"name": "InvalidRpcRequestError",
|
|
511
|
+
"message": "Invalid RPC request body. Expected { args: unknown[] }."
|
|
512
|
+
}
|
|
513
|
+
}
|
|
318
514
|
```
|
|
319
515
|
|
|
320
|
-
|
|
516
|
+
JSON 自体が不正な場合も `400` です。
|
|
517
|
+
|
|
518
|
+
### POST 以外
|
|
519
|
+
|
|
520
|
+
Local RPC endpoint は POST のみ受け付けます。
|
|
521
|
+
|
|
522
|
+
```http
|
|
523
|
+
GET /__gasboost/sum
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
は `405` になります。
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{
|
|
530
|
+
"error": {
|
|
531
|
+
"name": "MethodNotAllowedError",
|
|
532
|
+
"message": "Only POST is allowed."
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 未登録 RPC
|
|
538
|
+
|
|
539
|
+
存在しない RPC を呼び出した場合は `500` を返します。
|
|
540
|
+
|
|
541
|
+
```text
|
|
542
|
+
POST /__gasboost/unknown
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"error": {
|
|
548
|
+
"name": "Error",
|
|
549
|
+
"message": "Function unknown is not registered."
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Handler Error
|
|
555
|
+
|
|
556
|
+
RPC handler 内で例外が発生した場合も `500` として JSON で返されます。
|
|
557
|
+
|
|
558
|
+
```json
|
|
559
|
+
{
|
|
560
|
+
"error": {
|
|
561
|
+
"name": "TypeError",
|
|
562
|
+
"message": "handler failed"
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Local RPC の対象 path
|
|
568
|
+
|
|
569
|
+
次の形式だけを RPC として扱います。
|
|
570
|
+
|
|
571
|
+
```text
|
|
572
|
+
/__gasboost/{rpcName}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
例えば以下は対象です。
|
|
576
|
+
|
|
577
|
+
```text
|
|
578
|
+
/__gasboost/getUser
|
|
579
|
+
/__gasboost/sum
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
一方、次のような path は RPC として扱いません。
|
|
583
|
+
|
|
584
|
+
```text
|
|
585
|
+
/__gasboost/
|
|
586
|
+
/__gasboost/foo/bar
|
|
587
|
+
/api/users
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
通常の Vite middleware chain に処理を渡します。
|
|
591
|
+
|
|
592
|
+
query string が付いていても RPC 名は正しく解決されます。
|
|
593
|
+
|
|
594
|
+
```text
|
|
595
|
+
/__gasboost/hello?foo=bar
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
URL encoded な RPC 名も decode されます。
|
|
599
|
+
|
|
600
|
+
```text
|
|
601
|
+
/__gasboost/hello%20world
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
# build と dev の責務
|
|
607
|
+
|
|
608
|
+
```text
|
|
609
|
+
gasboost()
|
|
610
|
+
│
|
|
611
|
+
├─ build
|
|
612
|
+
│ ├─ AppsScript の静的解析
|
|
613
|
+
│ ├─ GAS グローバル関数生成
|
|
614
|
+
│ ├─ GAS 向け build config
|
|
615
|
+
│ └─ backend production build
|
|
616
|
+
│
|
|
617
|
+
└─ dev
|
|
618
|
+
├─ Vite Dev Server middleware
|
|
619
|
+
├─ Local RPC endpoint
|
|
620
|
+
├─ entry の ssrLoadModule
|
|
621
|
+
└─ AppsScript.dispatch
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
frontend と backend を1つの `vite.config.ts` で扱う場合は、mode で分岐します。
|
|
321
625
|
|
|
322
626
|
```ts
|
|
323
|
-
|
|
627
|
+
const { build, dev } = gasboost({
|
|
628
|
+
entry: "src/server.ts",
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
export default defineConfig(({ mode }) => {
|
|
632
|
+
if (mode === "server") {
|
|
633
|
+
return {
|
|
634
|
+
plugins: [build],
|
|
635
|
+
};
|
|
636
|
+
}
|
|
324
637
|
|
|
325
|
-
|
|
638
|
+
return {
|
|
639
|
+
plugins: [dev],
|
|
640
|
+
};
|
|
641
|
+
});
|
|
326
642
|
```
|
|
327
643
|
|
|
328
|
-
|
|
644
|
+
`build` を通常の frontend build に含めると、server entry が Vite の build input になるため、frontend と backend の build は分離してください。
|
|
329
645
|
|
|
330
646
|
## 関連パッケージ
|
|
331
647
|
|
|
332
|
-
|
|
648
|
+
- `@gasboost/app` — GAS バックエンドランタイムと RPC 定義
|
|
649
|
+
- `@gasboost/client` — フロントエンド側の型安全 RPC クライアント
|
|
650
|
+
|
|
651
|
+
## License
|
|
652
|
+
|
|
653
|
+
MIT
|
package/dist/build.d.ts
ADDED
package/dist/dev.d.ts
ADDED
package/dist/gasboost.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -114,11 +114,12 @@ function b(e) {
|
|
|
114
114
|
return t.join("\n");
|
|
115
115
|
}
|
|
116
116
|
//#endregion
|
|
117
|
-
//#region src/
|
|
117
|
+
//#region src/build.ts
|
|
118
118
|
function x(e) {
|
|
119
119
|
let t;
|
|
120
120
|
return {
|
|
121
|
-
name: "gasboost",
|
|
121
|
+
name: "gasboost:build",
|
|
122
|
+
apply: "build",
|
|
122
123
|
config() {
|
|
123
124
|
return _(e);
|
|
124
125
|
},
|
|
@@ -132,4 +133,89 @@ function x(e) {
|
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
//#endregion
|
|
135
|
-
|
|
136
|
+
//#region src/dev.ts
|
|
137
|
+
var S = class extends Error {
|
|
138
|
+
constructor(e) {
|
|
139
|
+
super(e), this.name = "InvalidRpcRequestError";
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
function C(e) {
|
|
143
|
+
return {
|
|
144
|
+
name: "gasboost:dev",
|
|
145
|
+
apply: "serve",
|
|
146
|
+
configureServer(t) {
|
|
147
|
+
t.middlewares.use(async (n, r, i) => {
|
|
148
|
+
if (!n.url) {
|
|
149
|
+
i();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
let a = n.url.split("?")[0];
|
|
153
|
+
if (!a.startsWith("/__gasboost/")) {
|
|
154
|
+
i();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let o = a.slice(12);
|
|
158
|
+
if (!o || o.includes("/")) {
|
|
159
|
+
i();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (n.method !== "POST") {
|
|
163
|
+
r.statusCode = 405, r.setHeader("Content-Type", "application/json; charset=utf-8"), r.end(JSON.stringify({ error: {
|
|
164
|
+
name: "MethodNotAllowedError",
|
|
165
|
+
message: "Only POST is allowed."
|
|
166
|
+
} }));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
let { args: i } = await w(n), a = await (await t.ssrLoadModule(e)).default.dispatch(decodeURIComponent(o), ...i);
|
|
171
|
+
r.statusCode = 200, r.setHeader("Content-Type", "application/json; charset=utf-8"), r.end(a.contents);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
r.statusCode = e instanceof S ? 400 : 500, r.setHeader("Content-Type", "application/json; charset=utf-8"), r.end(JSON.stringify({ error: e instanceof Error ? {
|
|
174
|
+
name: e.name,
|
|
175
|
+
message: e.message,
|
|
176
|
+
stack: e.stack
|
|
177
|
+
} : {
|
|
178
|
+
name: "UnknownError",
|
|
179
|
+
message: String(e)
|
|
180
|
+
} }));
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function w(e) {
|
|
187
|
+
return new Promise((t, n) => {
|
|
188
|
+
let r = [];
|
|
189
|
+
e.on("data", (e) => {
|
|
190
|
+
r.push(typeof e == "string" ? Buffer.from(e) : e);
|
|
191
|
+
}), e.on("error", n), e.on("end", () => {
|
|
192
|
+
let e = Buffer.concat(r).toString("utf-8");
|
|
193
|
+
if (!e) {
|
|
194
|
+
t({ args: [] });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let i;
|
|
198
|
+
try {
|
|
199
|
+
i = JSON.parse(e);
|
|
200
|
+
} catch {
|
|
201
|
+
n(new S("Invalid RPC request body. Expected valid JSON."));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (typeof i != "object" || !i || !("args" in i) || !Array.isArray(i.args)) {
|
|
205
|
+
n(new S("Invalid RPC request body. Expected { args: unknown[] }."));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
t({ args: i.args });
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/gasboost.ts
|
|
214
|
+
function T(e) {
|
|
215
|
+
return {
|
|
216
|
+
build: x(e),
|
|
217
|
+
dev: C(e.entry)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
export { T as gasboost };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gasboost/vite",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A Vite plugin for building @gasboost/app applications for Google Apps Script.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"google-apps-script",
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"vite": "^8.2.2"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"typescript": "^7.0.2"
|
|
39
|
+
"typescript": "^7.0.2",
|
|
40
|
+
"@gasboost/app": "1.0.1"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
40
44
|
},
|
|
41
45
|
"scripts": {
|
|
42
46
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|