generated at
Denoのモジュール管理について考える
hr
Denoのモジュール管理について考えているkeroxp2019/9/15
※ Denoでは他の言語で「ライブラリ」「パッケージ」「クレート」...など呼ばれている外部ソースコードのことを一括して「モジュール」と呼ぶようにしている
Denoのリモートモジュールは、URLでしかimportできない
Node.jsのようなnpmというパッケージマネージャ package.json に使うモジュールを記述する仕組みはない
とはいえ package.json Gemfile Gopkg.toml もやっぱり便利だし…
serverstという僕が作っているHTTPサーバーモジュールも、deno_stdに依存していてバージョンアップのたびにソースコード中のこういうimportを一括replaceみたいなのをしていたのだけど、正直めんどかった
ts
import * as path from "https://deno.land/std@v0.17.0/fs/path.ts ...
さいきんdeno-jaでおなじみのsyumaiさんがdemというDenoのモジュール管理システムを作った
demではGodepに影響を受けながらも、Deno(ESM)的モジュール管理の仕組みを提示していた
demのアイデアは、こうだ
ESModuleにはExport aggregationという仕組みがある
ts
export * from "..." export {some} from "..."
こういうやつ
"..." の部分のexportsを全部このモジュールでexportする的なやつ
これを使うと複数のモジュールのexportsを一括でまとめたり部分的に削ったりができる
demはなんやかんやして、最終的に vendor ディレクトリにこういうファイルを作る
ディレクトリの構造がDenoのキャッシュ管理とおなじになっているのがわかる
ts
import * as server from "https://deno.land/std@v0.16.0/http/server.ts"
普通ならこう書くのだが、こういう風に書くことができる
ts
import * as server from "./vendor/https/deno.land/std@v0.16.0/http/server.ts"
これはESMのモジュールエイリアスとでも呼ぶべきものだ
この2つの記述はDeno(というかESM的)には等価になる(最終的なexportsが同じなので)
このメリットとしては、リモートモジュールのモジュール識別子( https://... の部分)をソースコードから消せることだ
URL形式のモジュール識別子は、URLが変わったりバージョンを変えたりすると頻繁に修正しなくてはいけない
これはかなり骨の折れる作業で、僕も結構最初くらいからきっつと思っていた
特に、URLにバージョンが入った形式だとプロジェクトのすべてのimportをreplaceしないといけなくて大変だ
demはvendorに貼ったエイリアスにもバージョン番号が含まれているのがちょっと面倒というか、問題を解決していない印象も持った
というわけで僕はこのアイデアを少し変えてこんな仕組みを考えた
こんなの
modules.ts
const modules = { "https://deno.land/std": { version: "@v0.17.0", modules: [ "/testing/mod.ts", "/testing/asserts.ts", "/textproto/mod.ts", "/io/bufio.ts", "/io/readers.ts", "/io/writers.ts", "/strings/decode.ts", "/strings/encode.ts" ] } };
まずこんなかんじのモジュールリスト的なやつを作る
これはdemの dem.json に少し似ているがより簡素化されている。あとJSONじゃない
これはこういうことを記述している
https://deno.land/std 以下にあるURLモジュールを使います
/std の後に続くpostfix(バージョン)は @v0.17.0 です
さらにその後に続くパス(モジュールファイルたち)はこれらです
で、こんな感じのコードを書く
modules.ts
#!/usr/bin/env deno --allow-write import * as path from "https://deno.land/std@v0.17.0/fs/path.ts"; const modules = { "https://deno.land/std": { version: "@v0.17.0", modules: [ "/testing/mod.ts", "/testing/asserts.ts", "/textproto/mod.ts", "/io/bufio.ts", "/io/readers.ts", "/io/writers.ts", "/strings/decode.ts", "/strings/encode.ts" ] } }; async function ensure() { const encoder = new TextEncoder(); for (const [k, v] of Object.entries(modules)) { const url = new URL(k); const { protocol, hostname, pathname } = url; const scheme = protocol.slice(0, protocol.length - 1); const dir = path.join("./vendor", scheme, hostname, pathname); const writeLinkFile = async (mod: string) => { const modFile = `${dir}${mod}`; const modDir = path.dirname(modFile); await Deno.mkdir(modDir, true); const specifier = `${k}${v.version}${mod}`; const link = `export * from "${specifier}";`; const f = await Deno.open(modFile, "w"); await Deno.write(f.rid, encoder.encode(link)); console.log(`Linked: ${specifier}`); }; await Promise.all(v.modules.map(writeLinkFile)); } } ensure();
これを実行するとこんな感じのモジュールエイリアスが作られる
txt
vendor/ https/ deno.land/ std/ testing/ asserts.ts mod.ts io/ bufio.ts ....
使うときはこう
ts
import * as bufio from "./vendor/https/deno.land/std/io/bufio.ts"
demとの違いは、モジュール識別子にバージョン情報を入れないこと
より正確にれると、このモジュール識別子を自分で決められるということ
こうすると、deno_stdをバージョンアップしたいときは modules.ts のここを変えるだけでいい
ts
version: "@v0.17.0",
そうするとプロジェクトで使っているdeno_stdのコードが全部アップデートされるので管理が楽
どうだろうか? 僕はこういう仕組みのほうが現実的に使いやすいような気もするkeroxp