Deno + dntでCJS・ESMに対応したnpmパッケージを作ろう
3 行まとめ
- CJS、ESM に対応した npm パッケージが Deno + dntで簡単に作成できる
- Deno で開発できるので、Lint、Format、Test、TypeCheck が設定なしですぐに使える
- 
dntで作成した CJS、ESM のファイルに対して、それぞれ Node.js でもテストを実行してくれる
 Deno のモジュールを npm パッケージに変換するdnt
Deno のdntモジュールを使うと、Deno で実装したモジュールを CommonJS(CJS) と ESM、TypeScript に対応した npm パッケージとして公開できます。
実際、今回 Deno とdntを試してみましたが、Deno には Linter、Formatter、Test などが組み込まれているので、すぐに npm パッケージの作成に取り掛かれるのが良かったです。また、CICD の自動化(GitHub Actions)もとてもシンプルに実装できた印象がありました。
また、dntではビルド時に生成された CJS、ESM のファイルに対して、それぞれ Node.js でテストを実行してくれたりもします。
実際にモジュールを npm に公開する
実際に Deno とdntを使って、npm パッケージを作成してみましょう。
次のリポジトリに今回実装したコードがあります。
Deno でモジュールを実装
まず、cowsayを使ったシンプルなモジュールを作ります。
import cowsay from "npm:cowsay@1.5.0";
export function cowsayNus3() {
  return cowsay.say({ text: "Hello nus3!" });
}
このモジュールでは、npm:specifiersを使ってcowsayパッケージをインポートしています。npm:specifiers やSkypack、esm.sh経由で使用されるパッケージは、dntによってビルド後のpacakge.jsonの依存関係に追加されます。
{
  "dependencies": {
    "cowsay": "1.5.0"
  }
}
Deno でテストを実装
Deno で作成したモジュールに対するテストを追加します。
import { assertStringIncludes } from "https://deno.land/std@0.192.0/testing/asserts.ts";
import { cowsayNus3 } from "./index.ts";
Deno.test("Cow should say Hello nus3!", () => {
  assertStringIncludes(cowsayNus3(), "Hello nus3!");
});
ビルドして npm パッケージを作成
次に、dntを使って Deno で実装したモジュールを npm パッケージとしてビルドします。今回はルート直下にbuild_npm.tsというファイルを作成し、そこにdntの設定を追加します。
import { build, emptyDir } from "https://deno.land/x/dnt@0.37.0/mod.ts";
await emptyDir("./npm");
await build({
  entryPoints: ["./mod/index.ts"],
  outDir: "./npm",
  shims: {
    deno: true,
  },
  package: {
    name: "@nus3/cowsay-nus3",
    version: Deno.args[0],
    description: "String function that returns where the cow says Hello nus3",
    license: "MIT",
    repository: {
      type: "git",
      url: "git+https://github.com/nus3/deno-npm-package.git",
    },
    bugs: {
      url: "https://github.com/nus3/deno-npm-package/issues",
    },
  },
  postBuild() {
    Deno.copyFileSync("LICENSE", "npm/LICENSE");
    Deno.copyFileSync("README.md", "npm/README.md");
  },
});
作成したファイルをdeno run -A build_npm.ts {version}のように Deno で実行することで、対象のモジュールが npm パッケージとしてビルドされ、今回の場合、npmディレクトリに出力されます。
❯ deno run -A build_npm.ts 0.0.1
[dnt] Transforming...
[dnt] Running npm install...
added 47 packages, and audited 48 packages in 5s
3 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities
[dnt] Building project...
[dnt] Type checking ESM...
[dnt] Emitting ESM package...
[dnt] Emitting script package...
[dnt] Running post build action...
[dnt] Running tests...
> @nus3/cowsay-nus3@0.0.1 test
> node test_runner.js
Running tests in ./script/index_test.js...
test Cow should say Hello nus3! ... ok
Running tests in ./esm/index_test.js...
test Cow should say Hello nus3! ... ok
[dnt] Complete!
実行時のログを見るとわかりますが、dntではビルドで生成した ESM、CJS 両方の形式のファイルに対して、Deno で書いたテストを Node.js で実行してくれます。
サンプルリポジトリでは、example配下で公開したパッケージをrequire、import で呼び出せるか確認できるようにしてあります。
GitHub Action で CICD を整える
前述した記事に GitHub 上で Release を作成することで npm に公開する GitHub Actions が紹介されています。
ただ、せっかくなので今回は合わせて、Lint、Format、Test、TypeCheck が GitHub Actions 上で実行されるようにしましょう。
name: CI
on:
  pull_request:
jobs:
  ci:
    name: Check lint and test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      - name: Setup Deno
        uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x
      - name: Lint
        run: deno lint
      - name: Test
        run: deno test -A
      - name: Check format
        run: deno fmt --check
      - name: Type check
        run: deno check mod/**.ts
      - name: Build
        run: deno run -A build_npm.ts
Deno を実行できる環境を用意するだけで Lint、Format、Test、TypeCheck が使えるので、とてもシンプルに実装することができて良きですね。
あとがき
最後の GitHub Actions が特に顕著ですが、Deno をインストールすることで開発に必要なエコシステムが用意された状態で、tsconfig などの設定も要らず、すぐに開発に着手できるのがとても良い点だと感じました。
さらに、今回紹介したdntを使うことで、npm パッケージを作成する上で考慮しなければいけない点をdntが担ってくれます。
もちろん、2023/08/09 現在、dntのバージョンは 0.38.0 でまだメジャーバージョンではない点や、使用したい npm パッケージがまだ Deno ではサポートされていない可能性もあります。しかし、Deno はバージョンを上げるごとに Node.js との互換性がどんどん上がっているので、npm パッケージを作成する際に Deno + dntを選択肢の 1 つとして試してみるのはいかがでしょうか。




Discussion