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まで含んだマルチプラットホームでテストコードを動かしています。