0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESbuildのloaderオプションを使って任意のファイルをimportする

Posted at

esbuildには--loaderというオプションがあります。このオプションを使うと、任意の拡張子のファイルをimportし、コンパイル時に依存関係に含めることができます。

esbuild−loaderの話ではありません。

この機能を使うと、例えば以下のようなTypeScriptコードをJavaScriptコードへコンパイルできるようになります。

// 任意の拡張子をimportできる
import css from "foo.css";
import txt from "foo.txt";
import bin from "foo.bin";
import png from "foo.png";

--loaderオプションの使い方

loaderオプションは以下のように使います。

$ esbuild esbuild app.js --bundle --loader:.png=dataurl --loader:.svg=text

上記のコマンドの--loader:.png=dataurl--loader:.svg=text部分がloaderオプションです。

拡張子ごとに、--loader:.<拡張子>=<読み込み方法>の形で指定します。

loaderオプションで指定できる読み込み方法

loaderオプションには、拡張子ごとに読み込み方法を指定できます。

公式ドキュメントによると、以下のような読み込み方法が指定できます。

  • js: JavaScriptファイルとして扱う
  • ts: TypeScriptファイルとして扱う
  • jsx: JSXファイルとして扱う
  • tsx: TSXファイルとして扱う
  • json: JSONファイルとして扱う
  • css / global-css / local-css: CSSファイルとして扱う
  • text / binary / base64 / dataurl: import文をファイルの中身に置き換える
  • file: import文をパス文字列に置き換える
  • copy: import文はそのままだがパス部分だけ置き換える
  • empty: 空のファイルとして扱う(無視する)

loaderオプションを試してみる

--loaderオプションを指定して実際にコンパイルして、出力ファイルがどうなるか見てみます。

今回コンパイルするのは以下のファイルです。
.fooという未知の拡張子をコンパイルしてみます。

コンパイル対象のファイル
import foo from "./hello.foo";
console.log(foo);
hello.fooの内容
hello world!

何も指定しない場合

.fooなどという聞いたことない拡張子はコンパイルできないと言われました。当然ですね。

$ npx esbuild index.ts --bundle --outdir=dist
✘ [ERROR] No loader is configured for ".foo" files: hello.foo

    index.ts:1:16:
      1 │ import foo from "./hello.foo";
        ╵                 ~~~~~~~~~~~~~

1 error

--loader:.foo=textオプション

text形式を指定してみます。

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --loader:.foo=text

こちらは、import文だった所が文字列の"hello world!"に置き換えられました。
ファイルの内容がそのまま文字列としてbundleされます。
例えばsvgファイルなどに便利そうです。

出力されたjsファイル
// hello.foo
var hello_default = "hello world!";

// index.ts
console.log(hello_default);

--loader:.foo=binaryオプション

binary形式を指定してみます。

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --platform=node --loader:.foo=binary

このオプションでは、import文がUint8Arrayに置き換えられます。
画像データなどのバイナリをimportしたい時に使えそうです。

出力されたjsファイル
var __toBinaryNode = (base64) => new Uint8Array(Buffer.from(base64, "base64"));

// hello.foo
var hello_default = __toBinaryNode("aGVsbG8gd29ybGQh");

// index.ts
console.log(hello_default);

--loader:.foo=base64オプション

こちらのオプションはtextオプションに近いものの、import文のところが「ファイルの中身をbase64エンコードしたもの」になっています。
こちらのオプションも、バイナリデータをimportする場合に便利そうです。

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --loader:.foo=base64
出力されたjsファイル
// hello.foo
var hello_default = "aGVsbG8gd29ybGQh";

// index.ts
console.log(hello_default);

--loader:.foo=dataurlオプション

こちらはdata URL形式に置き換わります。
data URLはURLの一種なので、fetch()の引数に渡したり、<img>タグのsrc属性に渡したりすることができます。

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --loader:.foo=dataurl
出力されたjsファイル
// hello.foo
var hello_default = "data:text/plain;charset=utf-8,hello world!";

// index.ts
console.log(hello_default);

--loader:.foo=fileオプション

fileオプションでは、import文がファイルの中身ではなくファイルパスに置き換わります

出力ディレクトリを見ると、"hello-TO42AHOB.foo"というファイルが生成されており、このファイルへのパスが変数に入ります。

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --loader:.foo=file
出力されたjsファイル
// hello.foo
var hello_default = "./hello-TO42AHOB.foo";

// index.ts
console.log(hello_default);

--loader:.foo=copyオプション

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --loader:.foo=copy

こちらのオプションでは、import文が元の形のまま保持されます。

出力されたjsファイル
// index.ts
import foo from "./hello-TO42AHOB.foo";
console.log(foo);

注意点として、今回の場合のように、拡張子fooのようなファイルをimportしている時は、実行すると普通にランタイムエラーになるのでご注意ください。

実行時のエラーメッセージ
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".foo"

使い道はあまりわかりません。

--loader:.foo=emptyオプション

こちらのオプションを使用すると、importの結果が空になります。

$ npx esbuild index.ts --bundle --outdir=dist --format=esm --loader:.foo=empty

生成されたコードは長いですが、実際に実行してみるとconsole.logで空のオブジェクトが出力されます。

出力されたjsファイル
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));

// hello.foo
var require_hello = __commonJS({
  "hello.foo"() {
  }
});

// index.ts
var import_hello = __toESM(require_hello(), 1);
console.log(import_hello.default);

こちらのオプションは、あえてファイルをbundleさせたくない時に有用そうです。

TypeScriptエラーへの対処

ところで、上のように、fooといった未知の拡張子を持つファイルをimportすると、TypeScriptから怒られます。

Cannot find module './hello.foo' or its corresponding type declarations.ts(2307)

こういう場合は、型定義ファイル(.d.tsファイル)を作り、拡張子に対する型を宣言してやることで、エラーを回避できます。

foo.d.ts
declare module "*.foo" {
  const data: string; // ここの部分はLoaderオプションに合わせて、Uint8Arrayなど適切な型を指定する。
  export default data;
}

この時、トランスパイル時に指定する--loaderオプションの値に合わせて、exportされる変数の型を決める必要があります。
例えば、binaryオプションを使用しているときはUint8Arrayにする必要があります。

まとめ

  • esbuildの--loaderオプションを使用すると、任意の拡張子のファイルをimportし、それをトランスパイルできます。
  • --loaderオプションでは拡張子ごとにオプションを指定できます。
  • import文をどのように置き換えたいかに合わせて、file / text / binaryなど多様なオプションを設定することができます。
  • TypeScriptエラーが出る場合は、.d.tsファイルを記述することでエラーを抑制できます。

--loaderオプション、JS / TSファイル以外をbundleする際に便利そうです。

フロントエンドなどではバンドルサイズが気になるので、fileオプションを使うのがよさそうです。

バックエンドで使用する場合、FaaS環境のようなローカルファイルが読めない環境でも、バンドルしてしまえば通常の依存関係と同様に扱えるのが嬉しいポイントですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?