Open4

Denoをソースからビルドする

Hajime-sanHajime-san

最近 Deno の npm support および Node.js compability について知っておきたくなったので、まずは Deno を手元で動かせるようにしてみる。

基本的にはこのマニュアル通りに進めれば一発でビルドは通る、素晴らしい。
ビルドに際しては mold がビルド速度に寄与するのでこちらも活用されたい。
mold -run cargo build -q

このままでは毎度 ./target/debug/deno run でビルドした deno バイナリを実行する必要があるので

.bashrc
alias mydeno='path/to/deno/target/debug/deno'

と ailias を作っておくのも良いかもしれない。

Hajime-sanHajime-san

deno_land/deno repository は denoland/deno_std を git submodule として登録しているようで、
cli などのツール群と共に std をデバッグしたい場合の正攻法がイマイチ分からなかった。

deno および deno_std を両方 fork しておいて、.gitmodules を以下のように書き換えた。

.gitmodules
[submodule "deno_third_party"]
	path = third_party
	url = https://github.com/denoland/deno_third_party.git
	shallow = true
[submodule "test_util/std"]
	path = test_util/std
-	url = https://github.com/denoland/deno_std
+	url = https://github.com/YOUR_NAME/deno_std
	shallow = true
[submodule "test_util/wpt"]
	path = test_util/wpt
	url = https://github.com/web-platform-tests/wpt.git

submodule の情報を更新した後、git submodule update --init --recursive
ローカルの deno のプロジェクトルートから test_util/std にフォークした std のリポジトリが入るようになる。

以下不要

ここからさらに cli/deno_std.rs を見てみるとこんな記述がある。

cli/deno_std.rs
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

use deno_core::url::Url;
use once_cell::sync::Lazy;

// WARNING: Ensure this is the only deno_std version reference as this
// is automatically updated by the version bump workflow.
static CURRENT_STD_URL_STR: &str = "https://deno.land/std@0.173.0/";

pub static CURRENT_STD_URL: Lazy<Url> =
  Lazy::new(|| Url::parse(CURRENT_STD_URL_STR).expect("invalid std url"));

なので、このまま test_util/std/** 以下のソースを変更して cli 経由の動作をデバッグしようにも公式にホストされた std がインストールされてしまう。

まずは以下のようにディレクトリをホストする書き捨てスクリプトを用意しておき、deno run -A server.tsで起動する。

test_util/server.ts
import { serve } from "https://deno.land/std@0.173.0/http/server.ts";
import { serveDir } from "https://deno.land/std@0.173.0/http/file_server.ts";
import { resolve } from "https://deno.land/std@0.173.0/path/mod.ts";

serve((req) => {
    const pathname = new URL(req.url).pathname;
    if (pathname.startsWith("/")) {
      return serveDir(req, {
        fsRoot: resolve(...[Deno.cwd(), "std"]),
      });
    }
    // Do dynamic responses
    return new Response();
  }, {
    addr: `:8080`
  });

そして cli/deno_std.rs"https://deno.land/std@0.173.0/""http://localhost:8000/" に書き換えてしまう。

この状態で mydeno run -A npm:cowsay Hi! などのコマンドを打つと、
download http://localhost:8000/node/_stream.d.ts のようなログが見られ、ローカルのソースから std を引っ張ってくれるようになる。
これらは実行時に /home/USER_NAME/.cache/deno/deps/http/localhost_PORT8000 へキャッシュされるので
この運用だとソースを変更してコマンドを打つ前に毎度キャッシュクリアをする必要がある。

このやり方で良いのだろうか。。?

Hajime-sanHajime-san

動作環境

上記でとりあえずのデバッグ体制が整ったので動かしてみたい npm ライブラリを試していく。

以下は alias を通した下記の構成でバイナリを動かしていくこととする。

$ uname -a
Linux foobar 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ deno --version
deno 1.29.4 (debug, x86_64-unknown-linux-gnu)
v8 10.9.194.5
typescript 4.9.4

$ mydeno --version
deno 1.29.4 (debug, x86_64-unknown-linux-gnu)
v8 10.9.194.5
typescript 4.9.4

また、 std へのパスを環境変数に設定する。

$ export DENO_NODE_COMPAT_URL=file:///path/to/deno_std/
$ echo "${DENO_NODE_COMPAT_URL}"
file:///path/to/deno_std/

std は 0.173.0 で検証する。

Hajime-sanHajime-san

Storybook

上記の deno repository とは別の適当な deno プロジェクト用のディレクトリを作成したのちに
deno run -A sb@latest init --builder=vite
を実行する。プロジェクトの設定などを選択したのちに下記のエラーが出た。

Error: Empty filepath.

$ deno run -A npm:sb@next init --builder=vite
Error: Empty filepath.
    at pathDirname (deno:ext/node/02_require.js:50:13)
    at new Module (deno:ext/node/02_require.js:257:17)
    at Function.Module._resolveFilename (deno:ext/node/02_require.js:566:30)
    at Function.resolve (deno:ext/node/02_require.js:824:21)
    at getRendererDir (file:///path/to/.cache/deno/npm/registry.npmjs.org/@storybook/cli/7.0.0-beta.31/dist/generate.js:1:6676)
    at componentsPath (file:///path/to/.cache/deno/npm/registry.npmjs.org/@storybook/cli/7.0.0-beta.31/dist/generate.js:11:1474)
    at copyComponents (file:///path/to/.cache/deno/npm/registry.npmjs.org/@storybook/cli/7.0.0-beta.31/dist/generate.js:11:2661)
    at async baseGenerator (file:///path/to/.cache/deno/npm/registry.npmjs.org/@storybook/cli/7.0.0-beta.31/dist/generate.js:37:682)
    at async generator4 (file:///path/to/.cache/deno/npm/registry.npmjs.org/@storybook/cli/7.0.0-beta.31/dist/generate.js:46:1926)
    at async doInitiate (file:///path/to/.cache/deno/npm/registry.npmjs.org/@storybook/cli/7.0.0-beta.31/dist/generate.js:371:78)

この issue と関係してそうな雰囲気がある。

could not find npm package

次に、mydeno run -A npm:sb@next init --builder=vite で変更後の内容を適用した状態でコマンドを打つ。
諸々のインストールが済んだのちに下記のエラーが出た。

$ mydeno run -A npm:sb@next init --builder=vite
..

Error 'could not find npm package for 'file:///path/to/repository/github/ntd/test/.storybook/main.js'' contains boxed error of unknown type:
  "could not find npm package for 'file:///path/to/repository/github/ntd/test/.storybook/main.js'"

これはおそらくStorybook側の問題で、この issue と関係してそうな雰囲気がある。

  • Storybook cannot be built on packages using "type": "module"
    https://github.com/storybookjs/storybook/issues/11587#issuecomment-1192326066
    • npm:sb init コマンドの時点では現状 ESM 形式での .storybook/main.mjs 生成に対応していなさそうな雰囲気なので、ここは
    .storybook/package.json
    {
        ..
       "type": "module"
        ..
    }
    
    とファイルを配置しておくこととする。

Top-level await promise never resolved

再度、mydeno run -A npm:sb@next init --builder=vite を実行する。(上書きになるので -f オプションを付与する。)

$ mydeno run -A npm:sb@next init --builder=vite -f
..

🔎 checking possible migrations..
error: Uncaught (in worker "$DENO_STD_NODE_WORKER_THREAD") Top-level await promise never resolved
  [{ threadId, workerData, environmentData }] = await once(
                                                ^
    at <anonymous> (http://localhost:8000/node/worker_threads.ts:178:49)
error: Uncaught Error: Unhandled error. ('Top-level await promise never resolved')

この issue(std) と関係してそうな雰囲気がある。

コマンドラインが進まなくなる

再度、mydeno run -A npm:sb@next init --builder=vite を実行する。(上書きになるので -f オプションを付与する。)

$ mydeno run -A npm:sb@next init --builder=vite -f
..

To run your Storybook, type:

   npm run storybook 

For more information visit: https://storybook.js.org
  • Storybook の cli に対応する部分のコードを見てデバッグした限りは init コマンド自体は正常に終了しているものの、 deno のプロセスが正しく終了していないように見える。

https://github.com/storybookjs/storybook/blob/v7.0.0-beta.31/code/lib/cli/src/generate.ts#L37-L55

TypeError: worker.unref is not a function

さきほどのプロセスが終了しないというのはさておき、いったん必要なものはインストールされたので
次は dev コマンドを試してみる。
その前に、下記工程を済ませておく。

  • .storybook/package.json
    • もう不要なので削除する
  • .storybook/main.js
    • .storybook/main.mjs にリネームする
    • 中身を export default にする
    .storybook/main.mjs
    export default {
      "stories": [
        "../stories/**/*.mdx",
        "../stories/**/*.stories.@(js|jsx|ts|tsx)"
      ],
      "addons": [
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@storybook/addon-interactions"
      ],
      "framework": {
        "name": "@storybook/react-vite",
        "options": {}
      },
      "docs": {
        "autodocs": "tag"
      }
    }
    

この状態で mydeno run -A npm:sb@next dev で開発サーバーを起動する。

$ mydeno run -A npm:sb@next dev
@storybook/cli v7.0.0-beta.31

ERR! TypeError: worker.unref is not a function
ERR!     at startWorkerThreadService (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild/0.16.17/lib/main.js:2280:10)
ERR!     at transformSync (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild/0.16.17/lib/main.js:2008:29)
ERR!     at compile2 (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild-register/3.4.2/dist/node.js:4851:43)
ERR!     at Module._compile (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild-register/3.4.2/dist/node.js:2254:31)
ERR!     at Module._extensions..js (deno:ext/node/02_require.js:780:12)
ERR!     at Object.newLoader [as .mjs] (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild-register/3.4.2/dist/node.js:2262:9)
ERR!     at Module.load (deno:ext/node/02_require.js:658:34)
ERR!     at Function.Module._load (deno:ext/node/02_require.js:515:14)
ERR!     at Module.require (deno:ext/node/02_require.js:680:21)
ERR!     at require (deno:ext/node/02_require.js:820:18)
ERR!  TypeError: worker.unref is not a function
ERR!     at startWorkerThreadService (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild/0.16.17/lib/main.js:2280:10)
ERR!     at transformSync (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild/0.16.17/lib/main.js:2008:29)
ERR!     at compile2 (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild-register/3.4.2/dist/node.js:4851:43)
ERR!     at Module._compile (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild-register/3.4.2/dist/node.js:2254:31)
ERR!     at Module._extensions..js (deno:ext/node/02_require.js:780:12)
ERR!     at Object.newLoader [as .mjs] (file:///home/hajime/.cache/deno/npm/registry.npmjs.org/esbuild-register/3.4.2/dist/node.js:2262:9)
ERR!     at Module.load (deno:ext/node/02_require.js:658:34)
ERR!     at Function.Module._load (deno:ext/node/02_require.js:515:14)
ERR!     at Module.require (deno:ext/node/02_require.js:680:21)
ERR!     at require (deno:ext/node/02_require.js:820:18)

Node.js の Worker インターフェースには ref というメソッドが定義されているが、Deno の Node.js 互換 Worker インターフェースには現時点で実装されていないようだ。
https://github.com/nodejs/node/blob/v18.12.1/lib/internal/worker.js#L378
https://github.com/denoland/deno_std/blob/0.173.0/node/worker_threads.ts#L34
Deno は現在 Node.js の LTSである 18.12.1 を互換の対象としている。

https://github.com/denoland/deno_std/pull/3059