LoginSignup
3
1

Deno で esbuild を使った知見

Last updated at Posted at 2023-04-17

まえがき

最近 esbuild を使っていくつかプロダクトを作ったときに得た知見をまとめようと思います。

執筆時点では多くの機能がプラグイン頼りになっています。これらについての情報などがあるため、公式ドキュメントの Deno instead of node を一読することをおすすめします。

Import Map について

Deno ではブラウザ互換のインポート (import 文を使ったインポート; いわゆる ESM インポート) が可能で、以下のようなインポートをすることが可能です。

import * as lodash from 'https://deno.land/x/lodash@4.17.19/lodash.js';

これに加えて、 import map にも対応しています。次に示すように import map を用意することでモジュール名を指定してインポートする (エイリアスを用意する) ことができます。型注釈のインポートにも利用できます。

import_map.json
{
  "imports": {
    "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js",
    "lodash/types": "npm:@types/lodash@4.17.19"
  }
}
some.ts
// @deno-types="lodash/types"
import * as lodash from 'lodash';

このファイルを Deno で実行する際には deno.json の importMap プロパティにファイルパスを指定するか、import map の内容を直接記述します。

deno.json
{
  "importMap": "./import_map.json"
}
deno.json
{
  // deno.json のトップレベル
  "imports": {
    "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js",
    "lodash/types": "npm:@types/lodash@4.17.19"
  }
}

または CLI オプションで次のように指定します。

$deno run --importmap import_map.json some.ts

また、vscode では deno.importmap を指定することで補完を効かせることができます。esbuild でバンドルする方法は 外部モジュールを使う に示します。

Web ビルド最小構成

基本的には Vite などの高機能なバンドラを利用することを推奨します。

とりあえず最小の構成でビルドするサンプルです。公式ドキュメントにある通り、esbuild.stop() を呼ぶ必要があります。実用的なテンプレートは Web ビルド用テンプレート に記述します。

build.ts
import * as esbuild from 'esbuild';
import * as posix from 'posix';

await esbuild.build({
  entryPoints: [posix.resolve('src', 'script.ts')],
  bundle: true,
  outdir: posix.resolve('dist'),
  minify: true,
  sourcemap: 'external',
});

esbuild.stop();

Web ビルド用テンプレート

私が実際に使用しているテンプレートです。とりあえずビルドできると思います。

ファイルの変更を監視 (watch) する

esbuild.BuildContext.watch を呼び出すことで watch モードになります。処理は返却されるので入力待ちなどでメインプロセスを維持する必要があります。以下のソースでは Enter キーを押すことで強制再ビルドさせる機能を追加しています。q を入力したときに終了するなども可能です。デフォルトではビルド成功時には何も出力されずビルドされたかわからないためビルド結果を出力するプラグインを利用することをおすすめします (ビルド結果を出力する (プラグイン))。

const ctx = await esbuild.context(config);

await ctx.watch();
console.log('Watching...');

for await(const _ of readLines(Deno.stdin)) {
  // manually rebuild
  await ctx.rebuild().catch(() => {});
}

ビルドしながらサーバーをホスト (serve) する

esbuild.BuildContext.serve を呼び出すことで HTTP サーバーをホストすることができます。上述 の watch モードと同様にメインプロセスを維持する必要があります。

const ctx = await esbuild.context(config);

const { host, port } = await ctx.serve({
  servedir: config.outdir
});
console.log(`Serving on ${host}:${port}`);

for await(const _ of readLines(Deno.stdin)) {}

また、esbuild.BuildContext.watch を続けて呼び出すことで watch モードで HTTP サーバーを建てることができます。

const ctx = await esbuild.context(config);

const { host, port } = await ctx.serve({
  servedir: config.outdir
});
console.log(`Serving on ${host}:${port}`);

await ctx.watch();
console.log('Watching...');

for await(const _ of readLines(Deno.stdin)) {
  // manually rebuild
  await ctx.rebuild().catch(() => {});
}

JSX/TSX (React, preact など) を使う

deno.json に次のように JSX と JSX Fragment のファクトリの設定を記述します。

React の場合
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxFactory": "react.createElement",
    "jsxFragmentFactory": "react.Fragment"
  },
}
preact の場合
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxFactory": "preact.h",
    "jsxFragmentFactory": "preact.Fragment"
  },
}

同様の設定を esbuild に対しても行います。

import denoConfig from './deno.json' assert { type: 'json' };

const config: esbuild.BuildOptions = {
  // ...
  jsxFactory: denoConfig.compilerOptions.jsxFactory,
  jsxFragment: denoConfig.compilerOptions.jsxFragmentFactory,
};

ビルドされる側のファイルでは次のように利用します。

/** @jsxImportSource preact */

// @deno-types="preact/types"
import * as preact from 'preact';  // この import は必須 (のはず)

const renderApp = function(target: HTMLElement) {
  preact.render(<App />, target);
}

ビルド結果を出力する (プラグイン)

esbuild-plugin-result-deno を利用します。

import resultPlugin from 'esbuild-plugin-result';

const config: esbuild.buildOptions = {
  // ...
  plugins: [
    buildResultPlugin(),
    // and other plugins
  ],
};

これを利用すると次のようなビルド結果が出力されます。

23:31:06 Build succeeded in 62ms

同様のプラグインは onEnd コールバックを利用することで簡単に実装することができます (ソース を参照)。

外部モジュールを使う

プラグインを使ってバンドルする方法

esbuild-plugin-cache-deno を利用します。このプラグインはリモート URL のインポートを解決・キャッシュするほか、importmap に基づいてモジュール名を解決する機能があります。

import esbuildCachePlugin from 'esbuild-plugin-cache';
import importmap from './import_map.json' assert { type: 'json' };

const config: esbuild.BuildOptions = {
  // ...
  plugins: [
    esbuildCachePlugin({
      directory: posix.resolve('cache'),
      importmap,
    }),
  ],
};

外部モジュールのままにする方法

ビルドオプションの external フィールドを設定することで外部ファイルとして設定することができ、読み込み時に解決するようになります (公式ドキュメント)。

import esbuildCachePlugin from 'esbuild-cache-plugin';
import importmap from './import_map.json' assert { type: 'json' };

const config: esbuild.BuildOptions = {
  // ...
  external: ['https://esm.sh/preact*']
};

ファイルをコピーする

copy loader を使う方法

loader として copy を指定することでファイルをコピーすることができます。

const config: esbuild.BuildOptions = {
  // ...
  loader: {
    '.png': 'copy',
  }
};

プラグインを使う方法

esbuild-plugin-copy-deno を使います。

import copyPlugin from 'esbuild-plugin-sass';
import importmap from './import_map.json' assert { type: 'json' };

const config: esbuild.BuildOptions = {
  // ...
  plugins: [
    copyPlugin({
      // base directory of source files
      baseDir: './src',
      // base directory of destination files
      baseOutDir: './dist',
      // files should be copied
      files: [
        { from: 'imgs/*', to: 'imgs/[name][ext]' },
        { from: 'wasm/*', to: 'wasm/[name][ext]' },
      ],
    }),
  ],
};

Sass を使う

esbuild-plugin-sass-deno を使います。

import sassPlugin from 'esbuild-plugin-sass';

// 単体のファイルとして出力する
const config: esbuild.BuildOptions = {
  // ...
  plugins: [
    sassPlugin(),
  ],
};

// スクリプトを使ってページに埋め込む
const config: esbuild.BuildOptions = {
  // ...
  plugins: [
    sassPlugin({ loader: 'css' }),
  ],
};

Deno 向けにビルドする

Deno 標準のバンドラ deno bundle コマンドは非推奨となっており、esbuild や rollup などの利用が推奨されています。
esbuild の場合ブラウザ向けビルドや Node.js 向けビルドではインポートが CommonJS 形式になってしまうため、ビルドオプションに platformtarget を指定します (参考ソース)。

const config: esbuild.BuildOptions = {
  // ...
  platform: 'neutral',
  target: 'deno1',
};

GitHub Actions で linter と formatter を使う

denoland/setup-deno を使います。deno lint で静的解析、 deno fmt --check で formatter によるチェックを行えます。

name: lint, check format

on: push

permissions:
  contents: read

jobs:
  lint-and-fmt:
    runs-on: ubuntu-latest
    timeout-minutes: 1

    steps:
      - uses: actions/checkout@v3

      - name: Setup Deno
        uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x

      - name: Lint
        run: deno lint

      - name: Check format
        run: deno fmt --check
3
1
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
3
1