Zenn
🚀

Denoで作成したCLIツールをdntでNode.js互換に変換するメモ

2025/03/31に公開


DenoでCLIツールを作成したのですが、Node.jsでも使用できるようにしたくなりました。できれば二重管理はしたくないため方法を模索したところ、Deno公式からdntという変換用ツールが提供されていたので使用することにしました。CLIツールを変換する際にいくつか気を付けなければならない点があったため、dntを使用してjsrとnpmで公開するGitHub Actionsを設定するまでのメモを備忘のため残しておきます。

前提

$ deno -v
deno 2.2.5
$ grep "jsr:@deno/dnt" deno.lock
    "jsr:@deno/dnt@*": "0.41.3",
$ node -v
v22.14.0
$ npm -v
10.9.2

コード変換

公式GitHubに書かれている通りビルド用スクリプトを作成する。変換時にnpmコマンドが使用可能でなければエラーとなってしまうので、なければ事前にNode.jsをインストールしておく。

ビルドスクリプト例

./build_npm.ts
import { build, emptyDir } from "jsr:@deno/dnt";
import packageInfo from "./deno.json" with { type: "json" };

await emptyDir("./npm");

await build({
  entryPoints: [{
    kind: "bin",
    name: "your-package-cmd",
    path: "./mod.ts"
  }],
  outDir: "./npm",
  typeCheck: false,
  declaration: false,
  scriptModule: false,
  shims: {  // https://github.com/denoland/dnt?tab=readme-ov-file#shims
    deno: true,
  },
  package: {  // definition of package.json to publish to npm
    name: "your-package",
    version: packageInfo.version,
    description: "Description of your-package.",
    author: "yourname",
    license: "MIT",
    repository: {
      type: "git",
      url: "git+https://github.com/yourname/your-package.git"
    },
    keywords: [
      "some",
      "words",
      "here"
    ],
  },
  postBuild() {  // running steps after transpile
    Deno.copyFileSync("LICENSE", "npm/LICENSE");
    Deno.copyFileSync("README.md", "npm/README.md");
  },
});

特筆事項

バージョンの同期

deno.json内のバージョンと同期させるためにpackageInfoをインポートしてpackageInfo.versionを使用する。

CLIツールとして変換

公式で言及されている通り、CLIツールとして変換するには#!/usr/bin/env nodeを追加したりするためにentryPointsでそのように指定しなければならない。nameプロパティで指定した内容は生成されるpackage.json内に以下が追加される。

package.json
{
  ...,
  "bin": {
    "your-package-cmd": "./esm/mod.js"
  },
  ...
}

CJS対応の無効化

scriptModule: falseを指定することでCJS対応をスキップすることができる。私の場合はfalseにしなければエラーとなり変換できなかった。CLIツールの場合はわざわざCJSに対応してもあまり意味がないように思われるため、falseに設定するのが無難と思われる。

最低限のファイル出力

CLIツールとして変換する場合、型定義などは通常必要ないためdeclaration: falseを指定して型定義等の出力を抑制する。具体的にはmod.tsの変換結果が以下のように変化する。

  • declaration: true (=指定なし)
    mod.d.ts
    mod.d.ts.map
    mod.js
    
  • declaration: false
    mod.js
    

GitHub Actions

定義ファイル例

publish.yaml
name: Publish Package
on:
  push:
    branches:
      - main
jobs:
  publish:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@v4

      - name: Install Deno
        uses: denoland/setup-deno@v2
        with:
          deno-version: v2.x

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "lts/*"
          registry-url: "https://registry.npmjs.org"

      - name: Translate by dnt
        run: deno run -A ./build_npm.ts

      - name: Publish to npm
        run: npm publish --provenance
        working-directory: ./npm
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}

      - name: Publish to jsr
        run: deno publish

特筆事項

環境整備

先ほども書いたがコード変換にはnpmコマンドが使用可能である必要があるため、DenoとNode.js両方の環境をセットアップする。

作業ディレクトリ

変換後のコードはpackage.jsonを含めoutDirで指定したディレクトリに出力される。そのため、npm publishコマンドを実行する際は作業ディレクトリを出力先ディレクトリにする必要がある。

雑記

CLIツールの場合はentryPointsで色々指定しなければならないという部分を読んでおらず若干つまずきました。

参考文献

Discussion

ログインするとコメントできます