GAS環境有點小眾,Deno環境則更加冷門。
那如果在Node.js叫Deno去拿GAS的資料,會發生什麼事?
走過路過不要錯過,沒什麼用但很可愛的Deno,現在開始表演~
前天(Day18)聊到,我當時試用GAS環境的Web App服務,用doGet
設計了一個可以回傳資料的HTTP-based API。換言之,我把GAS環境部署為serverless function hosting,接著就要用Deno環境來玩玩看、向GAS環境發送HTTP GET請求。
使用Deno執行TypeScript腳本,向指定的GAS Web App發送HTTP GET請求,將Google Sheet的特定欄位之資料抓下來,並儲存為JSON格式。
ℹ️筆者使用的IDE兼容VS Code。
// MacOS/Linux
curl -fsSL https://deno.land/install.sh | sh
// Windows
irm https://deno.land/install.ps1 | iex
安裝Open VSX extension:Deno。
在.vscode/settings.json
加上:
{
"deno.enable": true,
"deno.lint": true,
// 請自行修改為你存放Deno腳本的目錄路徑
"deno.enablePaths": [
"denoScripts"
]
}
首先,用TypeScript撰寫文件[^2],讓未來的我可以知道這個API會回傳什麼:
type ApiResponse = OkResponse | ErrorResponse;
interface OkResponse {
status: "ok";
code: 200;
message: string;
data: { toolstackLink: string };
}
interface ErrorResponse {
status: "error";
code: 400 | 404 | 500;
message: string;
}
接著,填寫必要的config constants:[^3]
const OUTPUT_PATH: string = "./src/data/fetched/toolstackLink.json";
const BASE_API_URL: string = "https://script.google.com/macros/s/<ID>/exec";
const QUERY_KEY: string = "toolstackLink";
const url = new URL(BASE_API_URL);
url.searchParams.set("key", QUERY_KEY);
const API_URL: string = url.toString();
最後,寫個fetcher:
async function fetchLink(): Promise<void> {
try {
const response: Response = await fetch(API_URL, {
method: "GET",
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`Fetch failed: ${response.status} ${response.statusText}`
);
}
const responseData: ApiResponse = await response.json();
if (responseData.status !== "ok") {
console.log(responseData.message);
Deno.exit(1);
}
if (!responseData.data) {
console.log("No data received from API");
Deno.exit(1);
}
const jsonString: string = JSON.stringify(responseData.data, null, 2);
await Deno.writeTextFile(OUTPUT_PATH, jsonString);
console.log(`Saved toolstack link JSON to ${OUTPUT_PATH}`);
} catch (error: unknown) {
console.error(
"Error:",
error instanceof Error ? error.message : String(error)
);
Deno.exit(1);
}
}
await fetchLink();
這裡麻煩的是,由於我的doGet
會回傳一個假的HTTP response,所以我同樣的動作要額外再做一次🙃
個人覺得美好的地方是,我可以使用Deno.writeTextFile
與Deno.exit
,語法上比Node.js簡潔乾淨,不用import { writeFile } from "fs/promises"
。在小腳本的差別不大,如果是更複雜的腳本應該會很有感。
由於這個專案主要是基於Node.js與pnpm,所以這裡就依然讓pnpm來幫我代為呼叫Deno:"get-link": "deno run --allow-net --allow-write ./denoScripts/fetch-link.ts",
這裡指令顯得很冗長,是因為Deno安全機制的flag。[^4][^5]
由於腳本裡使用了fetch
,所以我們必須明確給予Deno--allow-net
的權限才能執行;同樣的,由於腳本裡使用了Deno.writeTextFile
,所以必須給予--allow-write
。
當下覺得挺好玩的,嘗試後確定可以無痛地於Node.js環境的專案,額外呼叫Deno來執行小腳本。Deno因為內建TypeScript,所以可以直接run TypeScript腳本。
同時,儘管只是小練習,我也可以想像為什麼那天那位德國工程師不喜歡Deno。畢竟工作上的專案都有時程壓力,Deno的安全機制會帶來小不便,反之Bun則可能更快更快樂。
總之,如果是work project,還是乖乖用Node.js就好。
[^1]: 是的,我當時嘗試在以Node.js為主環境的專案上使用Deno🤩
[^2]: 當下我覺得在這個小練習用TypeScript真的太heavy。
[^3]: 由於這是prebuild時執行的腳本,所以我把URL直接寫在腳本裡。
[^4]: Node.js誕生初期,Ryan只是想實作出讓JavaScript可以跑I/O,那時原本以為只是個人小專案,所以讓Node.js擁有完整系統存取權限,殊不知Node.js最終變成JavaScript生態系如此關鍵的環境。現在Ryan重頭來過,Deno就改成預設無權限,需要開發者與腳本使用者在每次執行腳本時主動給予權限。
[^5]: Node.js也採納了Deno這套機制,目前在最新版本有Permission Model。