iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
JavaScript

不只是快 —— Bun 30 天:從底層架構、全套工具鏈到生產部署系列 第 15

Bun Macros:編譯時執行程式碼的黑魔法(下篇- 實戰例子篇)

  • 分享至 

  • xImage
  •  

day15_title

前言

上一回我們講到 Macro 的基本操作,今天我們要講 Macro 比較進階的部分
可能有人在上一篇比較多理論和基本觀念的部分,對於實戰派的朋友,可能雲裡霧裡的
不過今天,我打算補齊上一篇留下來的伏筆,一些困難的部分範例,可以用 Macro 處理可以比較優雅的部分
今天會提供幾個實例去展示如何實現和解決相關問題的問題

實戰範例

內嵌 Git commit hash

其實這個範例,是官方提供的,我個人認為這相當實用,
因此我打算推薦給大家用

比如說有時候需要版本好或是展現現在的版本在哪個位置方便查察
可以當作版本 ID 作為使用,以方便確認版本在哪個位置
這對於 Debug 或是版本追蹤非常有用

我們建立一個 getGitCommitHash.ts

import { $ } from 'bun'

// 有看前面文章的知道可以這樣寫
export const getGitCommitHash = async (): Promise<string> => {
  return await $`git rev-parse HEAD`.text();
}

// === 這個是官方提供的 === 
// export function getGitCommitHash() {
//   const { stdout } = Bun.spawnSync({
//     cmd: ["git", "rev-parse", "HEAD"],
//     stdout: 'pipe',
//   });
//   return stdout.toString();
// }

這裡可以用 index.ts

index.ts

import { getGitCommitHash } from './getGitCommitHash' with { type: 'macro' }

console.log(`The current Git commit hash is ${getGitCommitHash()}`);

這樣我們就可以在打包時立即拿到當下的 git hash 其實相當簡單,我們看看結果

result_1

打包時直接發送 HTTP 請求

這個也是個相當實用的做法,不過也因為實用,這樣的運用有機會被濫用,
也就是說,可以在 build 的時候送出 HTTP 請求,這樣做就會有很多想像空間
比如 :

  • 把 build 的紀錄或是程式碼相關資訊在打包存儲到資料庫
  • 在 build 的時候把資料送到 server 觸發某些機制
  • 觸發 Lambda function 送通知串接 slack 或是 discord 之類的
  • 打包時解析結果把回應並內嵌進去到 jsx/tsx

...etc

很多想像空間可以再這期間做這些事情,那我們就開始做一個稍微的實作去達成這件事
我們來執行最後一個我提及的範例 「打包時解析結果把回應並內嵌進去到 jsx/tsx」

我們會用到 bun 提供的 HTMLRewriter

補充介紹 : 目前它是 bun 內部可以提供的 api 它是基於 cloudflare 的 lol-html 實作的,
可以取代特定 tag 內文或是文字
詳細文件可以參考

我們建立一個 meta.ts 用 macro 來進行內嵌

interface MetaResult {
  title: string
  [T: string]: string
}

export const extractMetaTag = async (url: string): Promise<MetaResult> => {
  const meta: MetaResult = { title: '' };
  const response = await fetch(url);
  // 這裡我們針對 拿到的 url 做 rewrite 
  new HTMLRewriter()
    .on('title', {
      text(element) {
        // 這裡我們加入一些 text
        meta.title += element.text;
      }
    })
    .on('meta', {
      element(element) {
        const name =
          element.getAttribute('name') ||
          element.getAttribute('porperty') ||
          element.getAttribute('itemprop');
        if (name) {
          const content = element.getAttribute('content');
          if (content !== null) {
            meta[name] = content;
          }
        }
      }
    })
    .transform(response);
  return meta;
}

建立一個 index.tsx

import { extractMetaTag } from './meta' with { type: 'macro' }

export const head = async () => {
  const headTag = await extractMetaTag('<這裡我們用 live-server 先起來,假設有自己的 template server 可以用自己的 url: 通常是 http://127.0.0.1:8080/>')

  return (
    <head>
      <title>{headTag.title}</title>
      <meta name="viewport" content={headTag.viewport} />
    </head>
  )
}

我們要用 live-server 把 要拿取的網站的 metaTag 還有 title 內嵌,到我們的網站

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>這是一個測試用的 title</title>
</head>
<body>
   這是一個測試的模板
</body>
</html>

跑起來

bunx live-server index.html

我們打包 index.tsx 看看

bun build ./index.tsx --outdir out

結果 :

result_2_sample

那我們看看打包完內部 ./out/index.js 內部長得如何

result_sample

結論

這篇我們從一個簡單到有點無聊的 Git hash 範例出發,一路走到「打包時發 HTTP 請求、抓資料、內嵌進 JSX」這種聽起來有點瘋狂的操作。
如果你跟著做到這裡,應該已經感覺到 Macro 不只是個「編譯時小工具」,
而是一個能重新定義 Build Time 與 Runtime 界線的機制。
它可以讓你的 Server-Side Rendering 更聰明、讓 Build 產物攜帶更多上下文,
但同時也打開了一扇門——一扇通往「build 時期副作用」的門。
這扇門後面藏著什麼?效能陷阱、快取策略、還是更多意想不到的應用場景?
下一篇,我們往應用走。


上一篇
Bun Macros:編譯時執行程式碼的黑魔法(上篇- 基礎篇)
下一篇
Elysia.js 入門:為 Bun 設計的 Type-safe 後端框架
系列文
不只是快 —— Bun 30 天:從底層架構、全套工具鏈到生產部署19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言