Deno1.16以降、Denoにはローカルファイルの読み込み方法が4種類存在します。
それぞれの利用方法と使い分けについて解説します。
方法1:ローカルファイルのFetch
Deno1.16以降、ローカルファイルのFetchが導入されました。
// import.meta.urlを使用して、このファイルからの相対パスを作成
const res = await fetch(new URL("./README.md", import.meta.url));
console.log(await res.text());
// console.log(await res.json()); // jsonの読み込み等も可能
メリット1:リモート実行に対応
この方法のメリットは、(例えば)github上のファイルを相対パスで読み込むことができることです。
> deno run --allow-net=raw.githubusercontent.com https://raw.githubusercontent.com/ayame113/deno_test/76d60f19850d362e214998f4ec141572f43f39dc/a.ts
# deno_test
上記のようにdeno run https://~
の形で実行すると、ファイルからの相対パスもhttps://
から始まるパスになります。ここで後述するDeno.readFile
等を使ってしまうと、リモートファイルの読み込みに対応していないため、エラーが出てしまいます。
deno run https://~
の形で実行する予定がある場合は(例えばCLIツール)、fetch
を使いましょう。
なお、これは「自ファイルからの相対パス」を使う場合の問題で、「カレントディレクトリからの相対パス」でファイル読み込みする場合は関係ありません。
メリット2:ファイルのストリーミングが可能
もう一つのメリットは、サーバーにおいてファイルのストリーミングが可能な事です。
import { serve } from "https://deno.land/std@0.114.0/http/mod.ts";
serve((request) => {
const relativePath = `.${new URL(request.url).pathname}`;
return fetch(new URL(relativePath, import.meta.url)); // Responseオブジェクトを返す
});
console.log("http://localhost:8000/");
ここで、serve
関数に渡すコールバックはResponse
オブジェクトです。そして、fetch
の返り値もResponse
オブジェクトです。
よって、実装としては、fetch
の返り値をそのままretuenするだけでOKです。
(Web APIを使用するメリットが生きていて、この設計がとても好きです。)
Response
オブジェクトはファイルの内容をストリーミングできるので、巨大ファイルの配信に適しています。
サーバーから静的ファイルを配信する場合、ストリーミング可能なfetch
を使いましょう。
Responseに対してContent-Typeを設定する方法は、長くなったので別記事に書きました。
また、標準ライブラリの関数を利用して、1行ずつの読み込みなども行うことができます。
import { readerFromStreamReader } from "https://deno.land/std@0.114.0/streams/mod.ts";
import { readLines } from "https://deno.land/std@0.114.0/io/mod.ts";
const response = await fetch("https://www.yahoo.co.jp/");
if (!response.body) {
throw new Error("cannot read response body");
}
const reader = readerFromStreamReader(response.body.getReader());
for await (const line of readLines(reader)) {
// 1行ごとに読み込み
console.log(line);
}
メリット3:deno deploy 互換性
ローカルファイルfetchはdeno deployでも利用可能です。
(2022年1月ごろに対応したようです。)
deno deployでfile://
始まりのURLへのfetchを行うと、GitHubリポジトリ内のファイルを読み込むことができます。
方法2:Deno.readTextFile
/ Deno.readFileSync
を使う
Deno.readTextFile
はテキストファイルを読み取る関数です。
// 非同期実行
const content1 = await Deno.readTextFile("./test.csv"); // カレントディレクトリからの相対パス
const content2 = await Deno.readTextFile(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス
// 同期実行
const content3 = Deno.readTextFileSync("./test.csv"); // カレントディレクトリからの相対パス
const content4 = Deno.readTextFileSync(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス
console.log(content1);
console.log(content2);
console.log(content3);
console.log(content4);
ファイルの内容全体を1度に読み取ってメモリに格納するため、大きいファイルを読むのには向いていません。
メリット1:同期APIが用意されている
Deno.readTextFile
は非同期関数なので、結果をawait
する必要があります。何らかの事情で非同期関数が使用できない時は、同期版のDeno.readTextFileSync
が使えます。
メリット2:fetch
に比べてawait
が少なく済む
fetchの例ではawait
を2回使用しています。
const res = await fetch(new URL("./README.md", import.meta.url));
console.log(await res.text());
一方、Deno.readTextFile
を使うとawait
が一回で済みます。
const content = await Deno.readTextFile(new URL("./README.md", import.meta.url));
console.log(content);
Deno.readTextFile
を使うとコードの見通しがよくなった感じがします。(これは好みの問題ですが)
方法3:Deno.readFile
/ Deno.readFileSync
を使う
Deno.readFile
はDeno.readTextFile
と似ていますが、返り値は文字列ではなくUint8Array
です。画像等のバイナリデータを読むのに向いています。
// 非同期実行
const content1 = await Deno.readFile("./test.csv"); // カレントディレクトリからの相対パス
const content2 = await Deno.readFile(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス
// 同期実行
const content3 = Deno.readFileSync("./test.csv"); // カレントディレクトリからの相対パス
const content4 = Deno.readFileSync(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス
// Uint8Arrayを文字列に変換するやつ
const decoder = new TextDecoder();
console.log(decoder.decode(content1));
console.log(decoder.decode(content2));
console.log(decoder.decode(content3));
console.log(decoder.decode(content4));
Uint8Array
を文字列に変換したい場合は、TextDecoder
を使います。
Deno.readTextFile
と同様、こちらも同期API(Deno.readFileSync
)が存在しています。
方法4:Deno.open
/ Deno.openSync
を使う
Deno.open
と標準ライブラリの組み合わせでも、ローカルファイルの読み込みができます。
import { readLines } from "https://deno.land/std@0.114.0/io/mod.ts";
const file = await Deno.open(new URL("./test.csv", import.meta.url));
try {
for await (const line of readLines(file)) {
console.log(line);
}
} catch (error) {
file.close();
throw new Error("failed to read file", { cause: error });
}
import { readAll } from "https://deno.land/std@0.114.0/streams/mod.ts";
const file = await Deno.open(new URL("./test.csv", import.meta.url));
const decoder = new TextDecoder();
try {
// await readAllの代わりにreadAllSyncも可能
const content = await readAll(file);
console.log(decoder.decode(content));
} catch (error) {
file.close();
throw new Error("failed to read file", { cause: error });
}
import { iterateReader } from "https://deno.land/std@0.114.0/streams/mod.ts";
const file = await Deno.open(new URL("./test.csv", import.meta.url));
try {
for await (const chunk of iterateReader(file)) {
console.log(chunk);
}
} catch (error) {
file.close();
throw new Error("failed to read file", { cause: error });
}
import { serve } from "https://deno.land/std@0.114.0/http/mod.ts";
import { readableStreamFromReader } from "https://deno.land/std@0.114.0/streams/mod.ts";
serve(async (request) => {
try {
// TODO: パスがディレクトリの場合を検出する
const file = await Deno.open(`.${new URL(request.url).pathname}`);
// ReadableStreamに変換
return new Response(readableStreamFromReader(file));
} catch {
return new Response("404 Not Found", { status: 404 });
}
});
console.log("http://localhost:8000/");
Deno.open
はファイルを開き、ファイルハンドラーを返す関数です。ファイルに対する操作が終わったら自分でfile.close()
を呼ぶ必要があります。
Deno.open
はgoライクなDeno.reader
インターフェースを返します。
標準ライブラリの関数のうちDeno.reader
に対応する関数を使って、ファイルのストリーミングをすることができます。特にreadLines
が便利です。
2022年1月ごろから、deno deployでも利用できるようになりました。
まとめ
||fetch
|Deno.readTextFile
|Deno.readTextFileSync
|Deno.readFile
|Deno.readFileSync
|Deno.open
|Deno.openSync
|
|--|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|deno deploy互換性|○|○|×|○|×|○|×|
|リモートファイル読み込み(deno run https://~
互換)|○|×|×|×|×|×|×|
|ストリーミング|○|×|×|×|×|○|○|
|同期or非同期|非同期|非同期|同期|非同期|同期|非同期|同期|
|戻り値|Promise<Request>
|Promise<string>
|string
|Promise<Uint8Array>
|Uint8Array
|Promise<Deno.Reader&Deno.ReaderSync>
|Deno.Reader&Deno.ReaderSync
|
|備考|Deno1.16以降のみ|||||標準ライブラリと組み合わせて使用|標準ライブラリと組み合わせて使用|
使い道に合わせた方法を選びましょう。
特に、大きなファイルを扱う場合は、パフォーマンスの観点からストリーミングに対応しているfetch
やDeno.open
を使ったほうがよいと思います。
また、deno deployではGitHubリポジトリ内のファイルをローカルファイルのように扱うことができます。deploy内では同期APIは使えず、非同期APIのみ対応しています。