🌐

Deno で https:// から import しても動くモジュールにする

2022/12/09に公開約6,900字

Deno Advent Calendar 2022 の9日目の記事です

https:// から import できるのはとても便利

Deno は Node.js とは違い, 直接 https:// から始まるURLでTypeScript のコードを import して実行することができます.

import { serve } from "https://deno.land/std@0.167.0/http/server.ts";

serve(() => {
  return new Response("今の時刻は" + new Date().toISOString() + "です");
});

time-server

package.json が不要で手軽ですね. import する URL は https://deno.land のオリジンである必要もなく, HTTP としてWebに公開していれば, どこからでも import できます. npm registry などにアップロードする必要はありません.

1番手軽に公開できるのは GitHub でしょう.

GitHub ではファイルのページの Raw ボタンから移動できる コードだけを返すURLを用意してくれています.

raw-url

https://raw.githubusercontent.com/narumincho/deno-https-import-test/main/sampleServer.ts

ただ この URL には commit hash が入っていないため, main ブランチのファイルを変更したら内容が変わってしまいます. Deno は 1度 import したものはキャッシュされ, 更新するのが少し面倒なのと, デプロイ時に内容が変わってしまうと開発時に動いたものと変わってしまうので, 「Copy Permalink」でコピーした commit hash が含まれる URL のページを開き「Raw」ボタンでURLを得るのが良いでしょう.

import { main } from "https://raw.githubusercontent.com/narumincho/deno-https-import-test/01c08b708bb21abdcda420eb1673a91ea1997678/sampleServer.ts";

main();

http-server-from-github

https://raw.githubusercontent.com/narumincho/deno-https-import-test/01c08b708bb21abdcda420eb1673a91ea1997678/sampleServer.ts のコードを確認すると次のように相対パスで指定してますが, うまく取得してくれます.

https://github.com/narumincho/deno-https-import-test/blob/01c08b708bb21abdcda420eb1673a91ea1997678/sampleServer.ts#L2

また, Deno Doc に URL を入れるとそのモジュールのドキュメントを表示してくれます. API ドキュメントの仕組みをわざわざ用意しなくて良いですね.

https://doc.deno.land/https://raw.githubusercontent.com/narumincho/deno-https-import-test/01c08b708bb21abdcda420eb1673a91ea1997678/sampleServer.ts

deno-doc

この https:// で始まるURL で import して動かせる機能ですが, うまく動かない場合があります.

import map を使っている場合

import map を使っているコード

https://github.com/narumincho/deno-https-import-test/blob/7c9a2c1caed59fcec84a794f266f23a59b84b111/useImportMap.ts

使う側

import { startUseImportMapHttpServer } from "https://raw.githubusercontent.com/narumincho/deno-https-import-test/d9c961328aff4e3512a550ac022fbccebdd988df/useImportMap.ts";

startUseImportMapHttpServer();

このコードを実行するとこのような import のパスが解決できないというエラーが発生します. (たまたま同じ指定の import map を使っていればエラーは発生しない)

PS C:\Users\narum\Documents\GitHub\deno-https-import-test> deno run --allow-net=:8000 ./start.ts
error: Relative import path "std/http/server.ts" not prefixed with / or ./ or ../
    at https://raw.githubusercontent.com/narumincho/deno-https-import-test/d9c961328aff4e3512a550ac022fbccebdd988df/useImportMap.ts:1:23

GitHub で公開したコードを直接使いたいときには import map を使わずに直接 import するモジュールを指定するのが良いでしょう.

ファイル読み取りをしている場合

今度は HTTP サーバーで画像を返す例です

https://github.com/narumincho/deno-https-import-test/blob/83d95134eafb394556f3b39de6255a7caf0bcd2a/imageServerUseReadFile.ts

  • import.meta.resolve を使っている理由は, このファイルを基準とした相対パスで指定するためです. そうしないとカレントディレクトリ基準になってしまいます
  • new URL を使っている理由は, file:// から始まる文字列には複数の解釈があるためみたいです. 関連 issue ↓

https://github.com/denoland/deno/issues/8579

使う側

import { startDenoImageServer } from "https://raw.githubusercontent.com/narumincho/deno-https-import-test/83d95134eafb394556f3b39de6255a7caf0bcd2a/imageServerUseReadFile.ts";

startDenoImageServer();

クローンして動かせば問題なく動くのですが, GitHub の https:// でimportするコードでは このようなエラーメッセージが表示され うまく動きません

PS C:\Users\narum\Documents\GitHub\deno-https-import-test> deno run --allow-net=:8000 ./start.tserror: Uncaught (in promise) TypeError: Must be a file URL.
    throw new TypeError("Must be a file URL.");
          ^
    at fromFileUrl (https://deno.land/std@0.167.0/path/win32.ts:968:11)    at startDenoImageServer (https://raw.githubusercontent.com/narumincho/deno-https-import-test/7c9a2c1caed59fcec84a794f266f23a59b84b111/imageServerUseReadFile.ts:9:5)    at file:///C:/Users/narum/Documents/GitHub/deno-https-import-test/start.ts:3:1

Deno.readFile ではなく fetch を使うことで対処できます. fetchhttps:// から始まるURL以外にも, import.meta.url などで取得できる file:// から始まるURLにも対応しています.

ちなみにHTTPの POST メソッドでファイルの保存はできません. できるのは HTTPのGETメソッドだけです

https://github.com/narumincho/deno-https-import-test/blob/83d95134eafb394556f3b39de6255a7caf0bcd2a/imageServerUseFetch.ts

import { startDenoImageServer } from "https://raw.githubusercontent.com/narumincho/deno-https-import-test/83d95134eafb394556f3b39de6255a7caf0bcd2a/imageServerUseFetch.ts";

startDenoImageServer();

これで, https:// から始まるURLで import しても動くようになりました

deno-image-server

また, 実行時に画像を取得するコードになっているため実行には

deno run --allow-net=:8000,raw.githubusercontent.com ./start.ts

のように --allow-net フラグに raw.githubusercontent.com の指定を加える必要があります. (:8000 の指定はHTTPサーバーの起動のため)

https://zenn.dev/hashrock/articles/dbd868e390d60f

https://deno.land/x/imagescript@1.2.15/utils/wasm/svg.js?source#L6

このフラグの指定をしなくて済み, またネットに接続していないときでも動作するようにするために画像をコードに埋め込むようにしてみます

この記事も似たようなことをしています

https://www.ccbaxy.xyz/blog/2021/06/05/js18/#deno-deploy-yong-nohuairuwo-bundle-sitemiru

png ファイルを json ファイルに変換して保存するコード

https://github.com/narumincho/deno-https-import-test/blob/83d95134eafb394556f3b39de6255a7caf0bcd2a/embedDenoPng.ts

json ファイルになった deno.png

https://github.com/narumincho/deno-https-import-test/blob/83d95134eafb394556f3b39de6255a7caf0bcd2a/deno.png.json

埋め込まれた json を import して動作するサーバーのコード

https://github.com/narumincho/deno-https-import-test/blob/83d95134eafb394556f3b39de6255a7caf0bcd2a/imageServerUseJsonImport.ts

import { startDenoImageServer } from "https://raw.githubusercontent.com/narumincho/deno-https-import-test/83d95134eafb394556f3b39de6255a7caf0bcd2a/imageServerUseJsonImport.ts";

startDenoImageServer();

これで, --allow-net に余計な指定をしなくて済むようになりました

start.ts
deno run --allow-net=:8000 ./start.ts

欠点は画像を変更したときに変更を反映させるために jsonに変換するスクリプトを実行して GitHub にアップロードする手間があることです. そこを自動化するのはまた今度ー

Discussion

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