iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
佛心分享-IT 人自學之術

我只是不想加班:一名客服人員的GAS自救之路系列 第 9

Day09|本地開發GAS專案:使用esbuild打包(上)

  • 分享至 

  • xImage
  •  

今天要來分享我個人(隨意粗暴地)使用esbuild,來讓我可以在本地使用import/export盡情拆分文檔,等到push前再打包回一個完整的bundle.js。嗯?你說是不是多此一舉?Yes!🤩

前言

昨天(Day08)聊到可以使用clasp來指定文件上傳順序,避免GAS環境合併檔案的順序不如預期而導致錯誤發生。可是,隨著專案規模變大與.js文檔被進一步拆分,就必須時常手動更新filePushOrder,除了維護上多了更多瑣碎步驟,整體專案架構可能也會越來越混亂。

Google官方對於檔案拆分的建議是妥善地運用class進行封裝[^1],或者使用Rollup預先將檔案做打包。[^2]在撰文的這個當下,我唯一實作過的專案則是選用相對輕量的esbuild來打包。

正文

實作步驟

①安裝esbuild

使用你偏好的套件管理工具進行安裝,例如pnpm或npm:

  • pnpm add -D esbuild
  • pnpm approve-builds
  • npm install -D esbuild

②集中單一入口點

開心地使用ES6+時代的import/export,入口點是triggers.js並且所有simple triggers都集中於此份文檔。

③建立一個build.js

import { build } from "esbuild";

build({
  outfile: "dist/bundle.js",
  target: "es2020",
  entryPoints: ["src/triggers.js"],
  bundle: true,
  platform: "neutral",
  format: "cjs",
  minify: false,
  treeShaking: false,
  sourcemap: false,
})
  .then(() => {
    console.log("Build completed successfully");
  })
  .catch((error) => {
    console.error(`Build failed:${error}`);
    process.exit(1);
  });

④設置package.json

"scripts": {
    "build": "node scripts/build.js",
  },

⑤打包

執行pnpm run buildnpm run build
以下是我的專案實際合併出來的psudocode:

"use strict";

SOME_CONSTANT
SPREADSHEET_ID

function helper(){}
function toColumnLetter(number) {}

SOME_METADATA
DYNAMIC_VARIABLES
function getColumnMapping(){}

function handler(){}
function pullFromCalendar(){}

function ui(){}
function createMenu(){}

function trigger(){}
function onOpen(){}

思路與權衡

  bundle: true,
  outfile: "dist/bundle.js",

首先,非常符合直覺的:我的核心需求就是打包成一份文檔,不用擔心沒有官方contract、不可預期的GAS環境「自動合併」機制。[^3]

  platform: "neutral",
  sourcemap: false,
  minify: false,

接著,也是很直覺的:GAS環境既非browser亦非node,也不支援sourcemap。由於檔案還沒有非常肥大,不啟用minify以維持可讀性與避免不可預期的錯誤。

target: "es2020",

這裡就讓我考慮了一會。不同於前端環境有表格可以參照不同瀏覽器支援的ES版本,我無法篤定GAS環境究竟目前採用哪些標準。這裡之所以選擇es2020,是因為上週(Day03)聊到的,globalThis被納入ES標準與GAS轉用V8引擎,都是在2020年。

  format: "cjs",
  treeShaking: false,
  entryPoints: ["src/triggers.js"],

接著這個最關鍵的、耗費了我最多時間,參考了其他前輩的配置,並來回試錯後,最終才權衡出適合我的專案的選項。直覺上,可能format會選擇iife,畢竟如昨天(Day08)提到的,GAS環境並不支援ES Modules。

可是,我們也記得上週(Day05)提到的,simple triggers必須位於global scope。這時,iife這種ES5-以前、全域物件幾乎等同於全域環境、沒有OER(old ECMAScript runtime)和DER(definitely even-worse runtime)之分[^4]的年代的做法,就把simple triggers都封印在汙染不到全域空間的無名function scope裡,任憑GAS環境如何呼喊(call),simple triggers就是聽不見。

那怎麼辦?雖然你已經知道了結局,請讓我留到明天再繼續說中間的故事。

後話

對我來說,僅聞esbuild這個工具其聲名遠播,直到這次實作才真的第一次使用它。畢竟,前端場景不論React或Vue,在撰文這個當下,都是使用Vite來打包了。讓我回憶起當年初次接觸CRA時,還試著跟著一個youtube影片從頭組裝webpack,結果到現在依然對webpack一知不到四分之一解。後來我第一次使用Vite是想玩玩看如今有點尷尬的PWA,多虧GenAI摘要文件的效率、又或者Vite的DX比較好,現在至少做環境設定時,可以更清楚自己在幹麻。是說,我現在才注意到,esbuild竟然就是Figma的其中一位創辦人Evan Wallace後來主持的開源專案。

Annotations

[^1]: 官方文件:「You are not limited to a single server Code.gs file. You can spread server code across multiple files for ease of development. All of the server files are loaded into the same global namespace, so use JavaScript classes when you want to provide safe encapsulation.」

[^2]: 官方文件(Clasp no longer transpiles typescript code. For typescript projects, use typescript with a bundler like Rollup to transform code prior to pushing with clasp. This has the advantage of offering more robust support for Typescript features along with ESM module and NPM package support.):「Clasp no longer transpiles typescript code. For typescript projects, use typescript with a bundler like Rollup to transform code prior to pushing with clasp. This has the advantage of offering more robust support for Typescript features along with ESM module and NPM package support.」

[^3]: 如昨天所述,就目前我所查閱到的官方文件,只有表示共用全域命名空間,並沒有闡明底層實作細節。

[^4]: 勘誤:此處應為OER (object environment record)與DER (declarative environment record)。如有造成混淆,心より謝罪申し上げます🙇‍♂️


上一篇
Day08|本地開發GAS專案:clasp指定上傳順序
下一篇
Day10|本地開發GAS專案:使用esbuild打包(下)
系列文
我只是不想加班:一名客服人員的GAS自救之路11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言