Denoでファイル操作
Denoには標準でファイル操作を行うことのできる関数がいくつか備わっています。
例えば、これらのものです。
Deno.readFileSync("filepath") // filepathの読み込み
Deno.writeFileSync("filepath") //filepathに書き込み
これらによって、Denoで様々なファイル操作(読み書き・コピー・削除など)が簡単に行えます。
そして、これらの関数の第1引数には対象のファイルパスを入れるのですが、ここには絶対パスだけでなく相対パスも使うことができます。
test/
̩̩├data/
│ └test.txt
└main.ts
上記のようなディレクトリ構成になっている場合、main.ts
を以下のようにして実行します。
const text = Deno.readTextFileSync("./data/test.txt");
console.log(text);
export {}
するとdataフォルダ内のtest.txt
ファイルの中身が出力されます。
相対パスの落とし穴
Denoのファイル操作関数の引数に与える相対パスのルートは、カレントディレクトリとなります。
下記の各コマンドはどれも同じmain.ts
を実行しているが、test.txt
が読み込めるのは一番上のカレントディレクトリをtest
直下にした時のみです。
/user/test$ deno run -A main.ts // OK
/user$ deno run -A ./test/main.ts // NG
/user/test/data$ deno run -A ../main.ts // NG
ただこのようなコマンドは誰でも実行してしまうのではないでしょうか?
別ディレクトリで作業してて、カレントディレクトリを移動するほどでもないけどmain.ts
を実行したいなって時とか。
解決案:pathResolver
の作成
pathResolver
とは、その名の通りパスを解決する(相対パス→絶対パス)ものです。
相対パスで問題が起こるなら、絶対パスに変換しちゃおうぜって考えです。
main.ts
を次のように変えてみましょう。
import * as path from "https://deno.land/std@0.79.0/path/mod.ts";
function pathResolver(meta: ImportMeta): (p: string) => string {
return (p) => path.fromFileUrl(new URL(p, meta.url));
}
const resolve = pathResolver(import.meta);
const text = Deno.readTextFileSync(resolve("./data/test.txt"));
console.log(text);
このようにすることで、resolve関数が相対パスを絶対パスに変換してくれるので、どのカレントディレクトリから実行しても同じ結果になります。
pathResolver
の解説
pathResolver
はimport.meta
を引数にとります。import.meta
は、そのファイル自体のファイルパスを格納しており、import.meta.url
で取得できます。これで取得できるのはファイルのURLなので、file:///~
で始まるものになります。
pathResolver
はそのmeta.url
を格納した新しい関数を返します。main.ts
ではこの関数の名前をresolve
としています。
このresolve
関数に相対パスを渡してあげればURLクラスがいい感じにパスの関係を計算してくれます。ファイルにアクセスするには最初のfile://
の部分は邪魔なので、Denoの標準モジュールstd
のpath
にあるfromFileUrl
関数で消して素のファイルパスを返します。
注意点
solver
はファイルごとに生成する必要があります。importしてはいけません。なぜならimport.meta
はファイルによって違うからです。
まとめ
ファイル操作系...というか相対パスを扱うときにはpathResolver
を使用しましょう。意図しないバグの発生を抑えることができます。
ちなみにimport文の相対パスは勝手に解決してくれるのでpathResolver
は使わなくて大丈夫です。
参考にしたコード
このpathResolver
は、僕がDenoでサーバを立てるときにお世話になっているモジュールservestのコードをヒントに作りました。
servestではpathResolver
を次のようにしています。
実際のコード(Github)
export function pathResolver(meta: ImportMeta): (p: string) => string {
return (p) => new URL(p, meta.url).pathname;
}
この記事内で示したものと外形は同じですが、pathモジュールを使っている所に違いがあります。
じつは、上記のコード1つ欠点があってWindowsではうまく動作しないということです。
Linux系(たぶんmacも)はパスの最初がスラッシュ/
で始まっているときに絶対パスとみなします。
一方、Windows系は絶対パスの最初はドライブ名で始まるということになっています。
new URL().pathname
では、import.meta.url
から取得したfile:///~
付きパスのfile://
しかとってくれません。Linux系だとスラッシュから始まることで絶対パスを表すので問題ないですが、Windowsではおかしくなります。
この問題を解決するのにpath
モジュールを使用しています。
(このpathモジュールを探すまでに時間かかったんだよなぁ…)
/*** Linux系では ***/
const urlLinux = "file:///home/test/test.txt";
const pathLinux = new URL("",urlLinux).pathname;
console.log(pathLinux);
// 出力:/home/test/test.txt
/*** Windowsでは ***/
const urlWin = "file:///c:/users/test/test.txt";
const pathWin = new URL("",urlWin).pathname;
console.log(pathWin);
// 出力:/c:/users/test/test.txt
// ↑パスの形式としておかしい
余談
ちなみにDenoのファイル操作系関数はURLクラスの引数にも対応してるっぽいのでpathResolver
はいらないっぽい?(未確認)
ただ、URLクラス未対応だけどファイルパスを引数にとる関数とかには使えるよ。