@ryoppippi

BunとNeovim

27 Sept 2023 ・ 8 min read


Note

この​記事はVim 駅伝の​ 9/27 の​記事です。

はじめに

Bunの​1.0が​発表されましたね! 自分は​すでに​いく​つかの​プロジェクトに​導入したりして、​とても​気に入っています。

https://github.com/ryoppippi/str-fns

例えば​こちらの​自作プロジェクトでは、​ node18 + pnpm + vitestから​bunに​移行した​ところ:

  • Packageの​インストールが​8秒から​0.5秒に
  • vitestから​bun testへの​移行で​5秒から​0.3秒に
  • tscに​よる​型チェックが​2.5秒から​1秒に

と、​とても​高速に​なりました。

また​モノレポにも​対応しているので、​手元の​プロジェクトたちを​順次pnpmから​bunに​移行しています。

Bunで​ペライチの​CLI Toolを​書く

さて、​Bunには​Auto Installと​いう​機能が​あります。

https://bun.sh/docs/runtime/autoimport

これは​スクリプトの​先頭に​ある​import文を​解析し、​必要な​パッケージを​自動で​インストールしてくれる​機能です。

例えば

import { z } from 'zod';

const schema = z.object({ unko: z.string() });
console.log(schema.parse({ unko: '💩' }));
 bun run main.ts
{
  unko: "💩"
}

のように、​自動的に​zodを​fetchし実行してくれます。 node_moduleを​作る​必要が​ないので、​気楽に​実行できますね。

また​先頭に​シェバンを​書く​ことで、./main.tsのように​実行する​ことも​できます。

#!/usr/bin/env bun
import { z } from 'zod';

const schema = z.object({ unko: z.string() });
console.log(schema.parse({ unko: '💩' }));
 chmod +x main.ts
 ./main.ts
{
  unko: "💩"
}

Neovim + Deno LSPで​Bun+ペライチの​CLI Toolを​書く

さて、​Bunで​CLI Toolを​書く​とき、​問題と​なるのは​LSPが​ない​ことです。 現状では​Bunの​LSPは​なく、​TSServerを​使わないと​いけません。 しかし​TSServerで​ライブラリの​情報を​補完するにはnode_modulesが​ないと​いけません。 つまり、​ペライチで​ nvim main.ts とか​やると、​補完が​効かなくて​悲しい​気持ちに​なります。 困りましたね。

…​ここでDenoの​出番です! Denoには​組み込みの​LSPが​あり、node_modulesが​なくても​ライブラリの​情報を​補完してくれます。 これを​うまく​使えば、​Bunで​ペライチの​CLI Toolを​書く​ことができるでしょう!

Denoのimportと​Bunのimportを​行き来する

Denoで​npmの​ライブラリを​使う​ときは、importの​パスにnpm:プレフィックスを​つけます。 逆に​いえば、​Deno LSPはnpm:が​ついていないimportの​ことは、​npmの​ライブラリと​して​認識してくれません。

import { z } from 'npm:zod'

const schema = z.object({ unko: z.string() })
console.log(schema.parse({ unko: '💩' }))

と​いう​ことで、​Bunのimportを​Denoのimportに​変換する​関数を​Luaで​書いてみましょう!

vim.api.nvim_create_user_command("BunToDeno", function()
    -- get current buffer text
    local text = vim.api.nvim_buf_get_lines(0, 0, -1, true)
    -- loop by lines
    for i, line in ipairs(text) do
        -- if the line stats with `import`
        if string.match(line, "^import") then
            -- import { hoge } from "hoge" => import { hoge } from "npm:hoge"
            -- import { hoge } from 'hoge' => import { hoge } from 'npm:hoge'
            text[i] = string.gsub(line, '"([^"]+)"', '"npm:%1"')
        end
    end
    -- set replaced text to current buffer
    vim.api.nvim_buf_set_lines(0, 0, -1, true, text)
end, {})

vim.api.nvim_create_user_command("DenoToBun", function()
    -- get current buffer text
    local text = vim.api.nvim_buf_get_lines(0, 0, -1, true)
    -- loop by lines
    for i, line in ipairs(text) do
        -- if the line stats with `import`
        if string.match(line, "^import") then
            -- import { hoge } from "npm:hoge" => import { hoge } from "hoge"
            -- import { hoge } from 'npm:hoge' => import { hoge } from 'hoge'
            text[i] = string.gsub(line, '"npm:([^"]+)"', '"%1"')
        end
    end
    -- set replaced text to current buffer
    vim.api.nvim_buf_set_lines(0, 0, -1, true, text)
end, {})

こうする​ことで​command一発で​Bunと​Denoの​importを​行き来できます! 便利ですね!

bun to deno
ちなみに: Neovim以外をお使いの方のために

TSで​実装した​CLIを​置いて​おきます。 Denoが​入っている​環境で​動きます。 https://github.com/ryoppippi/toDeno-toBun

ちなみに: Denoでよくない?

ここまで​読んで、​「素直に​Denoで​書けばいいじゃん」と​思った​方も​いるかもしれません。

#!/usr/bin/env deno run -A
import { z } from 'npm:zod';

const schema = z.object({ unko: z.string() });
console.log(schema.parse({ unko: '💩' }));

これで​全然​動きますし、​LSPも​使えるし、​現状色々​安定してるしDenoの​方が​良さげな​気もします。 ただし、​使いたい​ライブラリ(主に​npmに​転がっている​もの​)に​よっては、​Denoで​動かず、​Bunでは​動く、と​いう​ことがあります。 cleyeは​その​一例です​(まあDenoにはcliffyが​ありますが​)

自分も​Denoを​毎日​使っていますし、​Denoの​方が​安定しているとは​思いますが、​場合に​よっては​上記の​理由に​よる​Bunが​適している​場合も​ある​ため、​どちらも​使えるように​しておくと​幸せに​なれるかなと​考えています。

bun.lockbの​中身を​Neovimで​確認する

Bunにはbun.lockbと​いう​Lockfileが​あります(package-lock.jsonのような​もの)。 これはbun installを​実行すると​自動で​生成されます。

この​lockfileの​おかげで​依存関係の​解決が​高速に​なります。 しかし、​バイナリファイルである​ため、​中身を​見る​ことができません。 これは​ちょっと​不便ですね。 bun lock binary

実は​bunには​lockfileを​yarn v1形式で​出力する​機能が​あります。

https://bun.sh/docs/install/lockfile

これを​使うと、​lockfileの​中身が​読めるようになります。

bun bun.lockb

さて、​この機能を​使って、​Neovimで​lockfileを​開いたら​自動で​yarnの​lockfileに​変換するように​してみましょう。

vim.api.nvim_create_autocmd("BufReadCmd", {
    pattern = "bun.lockb",
    callback = function(ev)
        -- get the absolute path of the current file
        local path = vim.fn.expand("%:p")
        -- run command 'bun ' .. path and get stdout
        local output = vim.fn.systemlist("bun " .. path)
        -- set output to current buffer
        vim.api.nvim_buf_set_lines(0, 0, -1, true, output)
        -- set filetype to yarn.lock
        vim.opt_local.filetype = "conf"
        -- set readonly
        vim.opt_local.readonly = true
        -- set nomodifiable
        vim.opt_local.modifiable = false
    end,
})

これで、bun.lockbを​開くと、​自動で​yarnの​lockfileを​生成し、​一時的な​Bufferと​して​表示してくれます!

bun lock yarn

(ちなみに、​VSCodeを​お使いの​方は、Bun for Visual Studio Codeを​導入すると​自動で​lockfileが​yarn形式に​変換され​表示されます。​ただし​表示が​まあまあ遅いです。​Neovimだと​本当に​一瞬で​出てきますが​…​)

まとめ

楽しい​Bun Lifeを!

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi