Denoからnpmパッケージを使用するノウハウ
はじめに
DenoはJavaScript/TypeScriptランタイムであるため、既存のNode.jsやブラウザの資産をある程度活かすことができます。
この記事では、Denoからnpmパッケージを使用する際のノウハウについて、パッケージの種別ごとに解説します。
DenoとNode.jsの両方をサポートするパッケージ
npmパッケージの中にはDenoとNode.jsの両方をサポートしているパッケージがいくつか存在します。
例)
こういったパッケージを使用する際は、公式ドキュメントに使用方法が記載されている可能性が高いため、それに従うことを推奨します。
例として、Denoからkyパッケージを使用する方法を紹介します:
import ky from "https://unpkg.com/ky@0.24.0/index.js";
const res = await ky.get("https://qiita.com/api/v2/users?page=1");
console.log(await res.json());
Node.jsとブラウザの両方で動作するパッケージ
ブラウザとNode.jsのどちらの環境でも動作するモジュールはDenoでも動作する可能性が高いです。
このようなパッケージを使用する際は、Skypackまたはesm.shからimportすることをおすすめします(※理由については後述)
例として、fast-xml-parserパッケージを使用する方法を紹介します:
import parser from "https://cdn.skypack.dev/fast-xml-parser@3.17.6?dts";
const result = parser.parse(`<root>
  <user>
    <id>1</id>
    <name>hoge</name>
  </user>
</root>`);
console.log(result);
CommonJS形式で配布されているパッケージ
CommonJS形式で配布されており かつ Node.jsのAPIに依存しないパッケージについては、jspm.devからimportすると動作する可能性があります。
jspm.devから配信されるパッケージは、RollupによってES Modules形式へ変換されるためです。
例として、typescriptパッケージを使用する方法を紹介します(※@deno-typesについては後述します):
// @deno-types="https://unpkg.com/typescript@4.0.3/lib/typescript.d.ts"
import { default as ts } from "https://jspm.dev/typescript@4.0.3/lib/typescript.js";
const source = `
const a = 100;
const b = 200;
console.log(a + b);
`;
console.log(ts.transpileModule(source, {
  compilerOptions: {
    removeComments: true,
  }
}));
Node.jsのAPIに依存するパッケージ
Node.jsのAPI(fs, events等)に依存するパッケージについては、esm.shからimportすると動作する可能性があります(※理由については後述)
例として、fast-csvパッケージ(streamパッケージに依存)を使用する方法を紹介します:
import { parse } from "https://esm.sh/@fast-csv/parse@4.3.6?no-check";
import "https://deno.land/std@0.83.0/node/global.ts";
const stream = parse({ headers: true })
  .on("error", (err: Error) => console.error(err))
  .on("data", (row: Record<string, unknown>) => console.log(row))
  .on("end", () => console.log());
stream.write("id,name,age\n");
stream.write("1,hoge,20\n");
stream.write("2,piyo,30\n");
stream.write("3,fuga,40\n");
stream.end();
(補足) JavaScriptで書かれたnpmパッケージに型チェックや自動補完を適用する方法
DenoにはJavaScriptで書かれたパッケージに対して、TypeScriptによる型チェックや自動補完を適用するための方法がいくつか存在します。
例)
- 
X-TypeScript-TypesヘッダをサポートするCDNを利用する。 - 
@deno-typesを使用する。 
個人的な意見としては、以下の方針に従うのがよいのではないかと考えます:
- 基本的には、
X-TypeScript-TypesをサポートするCDN(Skypackまたはesm.sh)からモジュールをimportする。 - 
X-TypeScript-TypesをサポートしないCDN(jspm.devやUNPKG等)からモジュールをimportする際は、@deno-typesを使用する。 
 X-TypeScript-Typesとは?
まず、Denoでリモートモジュールをimportする際の挙動について簡単に解説します。
リモートモジュールとは、以下のようなものを指します:
import * as fs from "https://deno.land/std@0.83.0/fs/mod.ts";
import React from "https://esm.sh/react@16.14.0";
import faker from "https://cdn.skypack.dev/faker@5.1.0?dts";
- 
importされるモジュールがリモートモジュールであるか確認する。 - リモートモジュールであれば、それがローカルにキャッシュされているか確認する。
 - キャッシュされていなければ、HTTP/HTTPS経由でそのモジュールをダウンロードし、ローカルにキャッシュする。
 
Denoは上記3でリモートモジュールをダウンロードした際に、レスポンスにX-TypeScript-Typesヘッダが設定されているかを確認します。
もしX-TypeScript-Typesヘッダが設定されていれば、そこで指定されたファイルも一緒にダウンロードし、そのリモートモジュールに対する型定義ファイル(*.d.ts)として扱います。
この仕組みによって、JavaScriptで書かれたモジュールに対しても型チェック等を適用できるようになります。
以下にX-TypeScript-TypesヘッダをサポートしているCDNを紹介します:
 @deno-typesとは?
X-TypeScript-Typesヘッダ同様、JavaScriptで書かれたモジュールに型チェック等を適用するための仕組みです。
例えば、次のようなJavaScriptモジュールが存在したとします。
export function add(a, b) {
  return a + b;
}
以下は、このモジュールに対する型定義ファイルです。
export function(a: number, b: number): number;
add.jsをimportする際に、@deno-typesによってadd.d.tsを指定することで、型チェック等が有効化されます。
// @deno-types="./add.d.ts"
import { add } from "./add.js";
console.log(add(1, 2));
console.log(add("2", 3)); // コンパイルエラー!
(補足) CDNの紹介
Skypack
元々はPika CDNと呼ばれていました。
以下のような機能をサポートしています:
- HTTP/2
 - HTTP/3
 - Brotli
 - 
X-TypeScript-Typesヘッダ (以下の例のように、URLのクエリとしてdtsを付与すると設定されます) 
import faker from "https://cdn.skypack.dev/faker@5.1.0?dts";
	
console.log(faker.name.findName());
esm.sh
OSSとして開発されているCDNです。
以下のような機能をサポートしています:
- 
X-TypeScript-Typesヘッダ (URLのクエリにno-checkを付与することで無効化できます) - esbuildを使用したパッケージのES Modules形式への変換
 - Node.jsのネイティブモジュールをDenoのNode.js互換レイヤ(std/node)への読み込みに置換する
 - Bundle mode
 
おわりに
この記事ではDenoからnpmパッケージを使用するノウハウについて書きました!
Discussion
自分で開発してみてもいいかも。
部分的にコピペするなら両方のコードは普通に動きますが、大規模になると難しいでしょうね。
Deno と Node.js 共通ソースコードで開発する方法 - Qiita
うちのはWSHやGASのRhinoまで含んだマルチプラットホームでテストコードを動かしています。