1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[BigQuery] JavaScript UDF を Bun でバンドルしてみる

Posted at

概要

BigQuery の JavaScript UDF は活用できていますか?
外部の package を import するようなコードはバンドルして GCS に置く必要がありますが、これって結構面倒ですよね。

Bun を使ったらシンプルかも?と思って試したので備忘録を残します。最終的なコードはこちら。

Bun の導入

ちまたの記事では webpack が使われている印象ですが、 Bun はそれも含めて代替するような JavaScript / TypeScript 向けの all-in-one なツールキットです。

とりあえずインストールしましょう。
他のインストール方法も確認したい方はこちら

curl -fsSL https://bun.sh/install | bash # for macOS, Linux, and WSL

UDF を書く前に以下のように空の package.json を作っておきましょう。
普通は bun init コマンドを使いますが、今回は設定なしでどこまで動くか試してみます。

echo '{}' > package.json

UDF を書く

今回は YAML を JSON に変換する UDF を定義してみましょう。
YAML の解析を自力で実装するのは難しいので js-yaml をインストールします。

bun add js-yaml

このとき bun.lockb というファイルが生成されます。これは npm でいう package-lock.json です。ただし bun.lockb はバイナリファイルなので、 Git で管理するならこの辺りを一読するべきです。

さて YAML を JSON に変換する処理はこんな感じでしょうか。
末尾の console.log() は不要な気もしますが、これがないと全体が dead code と判断され後段で tree shaking されてしまうんですよね。賢すぎる。

yml2json.js
import * as yaml from "js-yaml";

const yml2json = {
  convert: (y) => {
    return JSON.stringify(yaml.load(y));
  },
};

console.log(yml2json);

GCS に配置

先ほど書いたコードをバンドルしてから GCS に配置します。

次のコマンドを実行すると ./out/yml2json.js が生成されます。これには js-yaml のコードもバンドルされています。超簡単!

bun build ./yml2json.js --outdir ./out

後は GCS に置くだけです。 BUCKET_NAME は各々書き換えてもらうとして、こんな感じ。

gsutil cp out/* gs://${BUCKET_NAME}

そうすると SQL から以下のように実行できます。

create temp function yml2json(yml string)
returns string
language js
options (library = ['gs://BUCKET_NAME/yml2json.js'])
as 'return yml2json.convert(yml)'
;

select yml2json('value: string value'); -- '{"value":"string value"}'

補足

bun build について補足します。これは BigQuery に特化した JavaScript ファイルを作成するコマンドではありません。デフォルトではブラウザで実行するためのファイルを生成します(詳しくはこの辺りを読んでください)。

BigQuery はブラウザ独自の API も Node / Bun 独自の API も実行できないですが、とりあえずブラウザ向けにバンドルしておけば動くことが多そうです。

例えば以下のコードは明らかに Node 独自の API を使っていますが、先ほどと同様に(デフォルトのブラウザ向けに)バンドルして GCS に配置すると正常に動きます。

base64.js
import { default as Buffer } from "node:buffer";

const base64 = {
  fromString: (s) => {
    const buf = Buffer.from(s, "utf8");
    return buf.toString("base64");
  },
};

console.log(base64);
declare input string default 'Hello, 世界';

create temp function string2base64(s string)
returns string
language js
options (library = ['gs://BUCKET_NAME/base64.js'])
as 'return base64.fromString(s)'
;

-- いずれも "SGVsbG8sIOS4lueVjA=="
select
  string2base64(input),
  to_base64(cast(input as bytes)), -- 一般的な関数で答え合わせ
;

どうもバンドルの過程で polyfill されて、独自の API が都合よく消えるようですね。

Bun automatically polyfills certain Node.js APIs when imported in the browser such as node:crypto, similarly to Webpack 4's behavior.
https://bun.sh/blog/bun-bundler#targets

最後に

まだ様々なケースを試したわけではないですが、わずかな設定でバンドルできるのはすごいですね。よければお試しあれ。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?