🦑

今の依存関係管理方法は?import map?import maps?って何?

2021/12/20に公開

これはDeno Advent Calendar 2021の20日目の記事です

前提

最近すっかりおざなりになっていたdenoで、また何か新しく作ろうかと思い、着手しようと思ったものの「そういえば依存関係の管理は今の流行りは何?」という雑な思いから始まりました
(ちなみにこういうものを作ってました)

https://github.com/lion-man44/cret | https://deno.land/x/pre_commit@0.1.4 | https://nest.land/package/simple-dotenv

そしてあれこれ読んでみたりしたものの非常に公式や他の方のまとめたドキュメントが読みづらいと感じてしまい、メモがてら自分でつけることにしました

なので既にご存知の方や詳細をもっと詳しく知りたいという方は、

https://github.com/WICG/import-maps

こちらなどを読むと詳しく知れるかと思います

結論

deps.json を使って --importmap or --import-map のオプションを使い、 deno run する

(e.g. deno run --importmap=deps.json mod.ts )

容易に追加したり削除したりする、要はnpm likeな何かが欲しいということであれば

https://deno.land/x/dep

をオススメします

import-maps とは何なのか

Deno自体はパッケージ管理を行う何かを持っていません( npm のようなものを)

そのため起動時にinstallするような挙動になっています(zero installと呼ばれています)

import { emptyDir } from "https://deno.land/std@0.117.0/fs/mod.ts";
console.log(emptyDir);
// => [AsyncFunction: emptyDir]

こういった感じで今まで指定していました

しかしこの指定方法は a.ts b.ts と数が増えていくごとに当然他のファイルでも使う場合に「まとまりがないため都度import文が必要になったり」「version指定をしていた場合は他のupdateする時も一々grepして、必要なファイル全てが変更になったり(まぁこれはある意味メリットでは?という見方もあるかもしれません)」、非常に面倒であると言わざるを得ません

ハードコーディングのつらみと言える部分です

その中でdenoには deps.ts の方にまとめておく、といった文化がありました

https://deno.land/x/oak@v10.0.0/deps.ts

いつ頃の仕様になるのかEcmaScriptの仕様として2018年ごろにimport mapsの仕様が完成されたみたいです

その実装方式自体は下記のような形になりました

<script type="importmap">
{
  "imports": {
    "vue": "node_modules/vue/dist/vue.common.prod.js"
  }
}
</script>

少し疑問が湧きましたが私の中ではまだ未解決です

  • HTML上でnode_modulesを指定する上でフォルダ構成を想像させてしまうので、攻撃者の理解の助けになってしまう可能性がある
  • importmapがHTMLに書かれている、かつlocal fileを指定している場合に、このリクエスト自体は一度local networkの外に出てから検索しに行くのか?

さて、denoではこれをjsonファイルとして読み込むことが可能になりました

// import-map.json
{
  "imports": {
    "fs/": "https://deno.land/std@0.117.0/fs/"
  }
}
// mod.ts
import { EOL } from "fs/eol.ts";
console.log(EOL);
// => { "LF": "\n", "CRLF": "\r\n" }

以前までは —-unstable な機能だったのですが、今ではstableになったので、 deno run のオプションとして —-importmap というオプションを持っています

できること

  • promise化されているので import(module).then ができる( dynamic import )
    • 上記のような形でできることの実際のメリットはまだ分からない(そのmoduleが読み込まれた時点でloading iconとかを動かしたりとか?)
import('https://deno.land/std@0.117.0/fs/mod.ts')
.then((mod) => {
    console.log(mod);
});
/* =>
Module {
  EOL: { LF: "\n", CRLF: "\r\n" },
  _createWalkEntry: [AsyncFunction: _createWalkEntry],
  _createWalkEntrySync: [Function: _createWalkEntrySync],
  detect: [Function: detect],
  emptyDir: [AsyncFunction: emptyDir],
  emptyDirSync: [Function: emptyDirSync],
  ensureDir: [AsyncFunction: ensureDir],
  ensureDirSync: [Function: ensureDirSync],
  ensureFile: [AsyncFunction: ensureFile],
  ensureFileSync: [Function: ensureFileSync],
  ensureLink: [AsyncFunction: ensureLink],
  ensureLinkSync: [Function: ensureLinkSync],
  ensureSymlink: [AsyncFunction: ensureSymlink],
  ensureSymlinkSync: [Function: ensureSymlinkSync],
  exists: [AsyncFunction: exists],
  existsSync: [Function: existsSync],
  expandGlob: [AsyncGeneratorFunction: expandGlob],
  expandGlobSync: [GeneratorFunction: expandGlobSync],
  format: [Function: format],
  move: [AsyncFunction: move],
  moveSync: [Function: moveSync],
  walk: [AsyncGeneratorFunction: walk],
  walkSync: [GeneratorFunction: walkSync]
}
*/
  • --importmap に与えられるファイル名は自由
  • 直接のfile自体を読み込むことも可能だが、最後を / にすることでその配下を全て事前importが可能 ( EOL の例を見てください)
    • "fs/": "https://deno.land/std@0.117.0/fs/*" といったアスタリスクを使用することはできなかった
  • local fileを指定する場合は下記のような形でファイルを取得することができる
    • const Bconsole.log(B) をcomment outしてもerrorが出ることはないです
// deps.json
{
  "imports": {
    "test": "./b.ts"
  }
}
// b.ts
export const B = 123
// a.ts
import { B } from 'test';
console.log(B);
// => 123
  • override optionとして scopes propertyが使える(上の例を再利用します)
    • 用途としてはimportしたはいいけど特定部分でerrorを出してしまう場合にmonkey patchを当てる用途です
// deps.json
{
  "imports": {
    "test": "./b.ts"
  },
  "scopes": {
    "./": {
      "test": "./c.ts"
    }
  }
}
// c.ts
export const B = "updated"
// a.ts
import { B } from "test";
console.log(B);
// => "updated"

まとめ

決定的に素晴らしいもの、というのはまだ掴めてないですし、これの背景はよく分からなかったですが、少なからず本体の方ではまぁ導入するだけあって温度感は高いものでした

しかし実際にはまだ dep みたいな外側からそれを扱うツールが必要最低限の機能だけになっているので npmyarn のような感じで使うと物足りない感じがあるかもしれません

それにsemver rangeが導入されていないため、自動アップデートによる追従などができずにちょっと面倒という感じもあります(ただその issue についても既に立っており、大方好意的な捉え方がされているみたいですがその後の返答がないので何かあったのでしょうか、といったところです)

Denoはまだまだ成長途中かついろいろなものが不安定の中動いている成長途中のプログラミング言語です

ほぼほぼTypeScriptを知っている人であれば、使うことができます。その不安定さも含めて今後が楽しみなプロダクトです

Discussion